diff --git a/.github/workflows/nuget-tag-publish.yml b/.github/workflows/nuget-tag-publish.yml index b4d2b1f3..d8d985d8 100644 --- a/.github/workflows/nuget-tag-publish.yml +++ b/.github/workflows/nuget-tag-publish.yml @@ -12,11 +12,6 @@ jobs: steps: - uses: actions/checkout@v1 - - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.300 - name: Install dotnet tool run: dotnet tool install -g dotnetCampus.TagToVersion diff --git a/demo/dotnetCampus.Ipc.Demo/Program.cs b/demo/dotnetCampus.Ipc.Demo/Program.cs index 187dbebb..a9c83495 100644 --- a/demo/dotnetCampus.Ipc.Demo/Program.cs +++ b/demo/dotnetCampus.Ipc.Demo/Program.cs @@ -3,8 +3,9 @@ using System.Reflection; using System.Text.Json; using System.Threading.Tasks; -using dotnetCampus.Ipc.PipeCore; -using dotnetCampus.Ipc.PipeCore.Context; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Pipes; namespace dotnetCampus.Ipc.Demo { @@ -59,7 +60,7 @@ private static void Main(string[] args) //Console.WriteLine(string.Join(",", Encoding.UTF8.GetBytes("ACK").Select(temp => "0x" + temp.ToString("X2")))); //var byteList = BitConverter.GetBytes((ulong) 100); - //Console.WriteLine(sizeof(ulong)); + //Console.WriteLine(sizeof(ulong)); //var peerRegisterProviderTests = new PeerRegisterProviderTests(); //peerRegisterProviderTests.Run(); diff --git a/demo/dotnetCampus.Ipc.Demo/dotnetCampus.Ipc.Demo.csproj b/demo/dotnetCampus.Ipc.Demo/dotnetCampus.Ipc.Demo.csproj index 27c70f1d..69d90982 100644 --- a/demo/dotnetCampus.Ipc.Demo/dotnetCampus.Ipc.Demo.csproj +++ b/demo/dotnetCampus.Ipc.Demo/dotnetCampus.Ipc.Demo.csproj @@ -1,13 +1,14 @@ - + Exe netcoreapp3.1 enable + + false - diff --git a/demo/dotnetCampus.Ipc.WpfDemo/ConnectedPeerModel.cs b/demo/dotnetCampus.Ipc.WpfDemo/ConnectedPeerModel.cs index 1934c86e..4008f120 100644 --- a/demo/dotnetCampus.Ipc.WpfDemo/ConnectedPeerModel.cs +++ b/demo/dotnetCampus.Ipc.WpfDemo/ConnectedPeerModel.cs @@ -2,8 +2,10 @@ using System.Collections.ObjectModel; using System.IO; using System.Windows.Threading; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.PipeCore; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Pipes; namespace dotnetCampus.Ipc.WpfDemo { @@ -22,7 +24,7 @@ public ConnectedPeerModel(PeerProxy peer) private void Peer_MessageReceived(object? sender, IPeerMessageArgs e) { - var streamReader = new StreamReader(e.Message); + var streamReader = new StreamReader(e.Message.Body.ToMemoryStream()); var message = streamReader.ReadToEnd(); Dispatcher.InvokeAsync(() => diff --git a/demo/dotnetCampus.Ipc.WpfDemo/MainWindow.xaml.cs b/demo/dotnetCampus.Ipc.WpfDemo/MainWindow.xaml.cs index 896551da..51b7bb6e 100644 --- a/demo/dotnetCampus.Ipc.WpfDemo/MainWindow.xaml.cs +++ b/demo/dotnetCampus.Ipc.WpfDemo/MainWindow.xaml.cs @@ -15,10 +15,11 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Pipes; using dotnetCampus.Ipc.WpfDemo.View; + using Walterlv.ThreadSwitchingTasks; namespace dotnetCampus.Ipc.WpfDemo @@ -76,7 +77,21 @@ private void StartServer(string serverName) ServerNameTextBox.Text = serverName; } + +/* 项目“dotnetCampus.Ipc.WpfDemo (net45)”的未合并的更改 +在此之前: private void IpcProvider_PeerConnected(object? sender, PipeCore.Context.PeerConnectedArgs e) +在此之后: + private void IpcProvider_PeerConnected(object? sender, PeerConnectedArgs e) +*/ + +/* 项目“dotnetCampus.Ipc.WpfDemo (net45)”的未合并的更改 +在此之前: + private void IpcProvider_PeerConnected(object? sender, Context.EventArgs.PeerConnectedArgs e) +在此之后: + private void IpcProvider_PeerConnected(object? sender, PeerConnectedArgs e) +*/ + private void IpcProvider_PeerConnected(object? sender, Context.PeerConnectedArgs e) { AddPeer(e.Peer); } diff --git a/demo/dotnetCampus.Ipc.WpfDemo/dotnetCampus.Ipc.WpfDemo.csproj b/demo/dotnetCampus.Ipc.WpfDemo/dotnetCampus.Ipc.WpfDemo.csproj index 71211365..99294541 100644 --- a/demo/dotnetCampus.Ipc.WpfDemo/dotnetCampus.Ipc.WpfDemo.csproj +++ b/demo/dotnetCampus.Ipc.WpfDemo/dotnetCampus.Ipc.WpfDemo.csproj @@ -5,11 +5,13 @@ netcoreapp3.1;net45 true enable - + + false + - + \ No newline at end of file diff --git a/dotnetCampus.Ipc.sln b/dotnetCampus.Ipc.sln index 3e89756b..7114e269 100644 --- a/dotnetCampus.Ipc.sln +++ b/dotnetCampus.Ipc.sln @@ -1,25 +1,19 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.30523.141 +VisualStudioVersion = 16.0.31729.503 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5D196596-756D-45C2-8A05-C8E4AB8A36E6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Ipc", "src\dotnetCampus.Ipc\dotnetCampus.Ipc.csproj", "{F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C0B9B0B5-D172-4309-A8C4-2C8B77E470CD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Ipc.Tests", "tests\dotnetCampus.Ipc.Tests\dotnetCampus.Ipc.Tests.csproj", "{E007FBCE-2F83-499F-9060-7D1FB673E24B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Ipc.PipeCore", "src\dotnetCampus.Ipc.PipeCore\dotnetCampus.Ipc.PipeCore.csproj", "{AA3EB068-A211-43C4-955D-F54C4EC9E492}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Ipc.Abstractions", "src\dotnetCampus.Ipc.Abstractions\dotnetCampus.Ipc.Abstractions.csproj", "{DD4F9A11-F789-4D0D-B581-718B1046661B}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Ipc.Demo", "demo\dotnetCampus.Ipc.Demo\dotnetCampus.Ipc.Demo.csproj", "{4D078B2A-029B-4B53-8FA9-5D2F9330BC72}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{65FA7B8E-7D2E-41D1-9740-BBB7D8B8ABE3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnetCampus.Ipc.WpfDemo", "demo\dotnetCampus.Ipc.WpfDemo\dotnetCampus.Ipc.WpfDemo.csproj", "{0E10A5EB-7E48-408A-A4E7-8AB2A7AF143A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Ipc.WpfDemo", "demo\dotnetCampus.Ipc.WpfDemo\dotnetCampus.Ipc.WpfDemo.csproj", "{0E10A5EB-7E48-408A-A4E7-8AB2A7AF143A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -55,30 +49,6 @@ Global {E007FBCE-2F83-499F-9060-7D1FB673E24B}.Release|x64.Build.0 = Release|Any CPU {E007FBCE-2F83-499F-9060-7D1FB673E24B}.Release|x86.ActiveCfg = Release|Any CPU {E007FBCE-2F83-499F-9060-7D1FB673E24B}.Release|x86.Build.0 = Release|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Debug|x64.ActiveCfg = Debug|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Debug|x64.Build.0 = Debug|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Debug|x86.ActiveCfg = Debug|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Debug|x86.Build.0 = Debug|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Release|Any CPU.Build.0 = Release|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Release|x64.ActiveCfg = Release|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Release|x64.Build.0 = Release|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Release|x86.ActiveCfg = Release|Any CPU - {AA3EB068-A211-43C4-955D-F54C4EC9E492}.Release|x86.Build.0 = Release|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Debug|x64.ActiveCfg = Debug|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Debug|x64.Build.0 = Debug|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Debug|x86.ActiveCfg = Debug|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Debug|x86.Build.0 = Debug|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Release|Any CPU.Build.0 = Release|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Release|x64.ActiveCfg = Release|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Release|x64.Build.0 = Release|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Release|x86.ActiveCfg = Release|Any CPU - {DD4F9A11-F789-4D0D-B581-718B1046661B}.Release|x86.Build.0 = Release|Any CPU {4D078B2A-029B-4B53-8FA9-5D2F9330BC72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4D078B2A-029B-4B53-8FA9-5D2F9330BC72}.Debug|Any CPU.Build.0 = Debug|Any CPU {4D078B2A-029B-4B53-8FA9-5D2F9330BC72}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -108,10 +78,7 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272} = {5D196596-756D-45C2-8A05-C8E4AB8A36E6} {E007FBCE-2F83-499F-9060-7D1FB673E24B} = {C0B9B0B5-D172-4309-A8C4-2C8B77E470CD} - {AA3EB068-A211-43C4-955D-F54C4EC9E492} = {5D196596-756D-45C2-8A05-C8E4AB8A36E6} - {DD4F9A11-F789-4D0D-B581-718B1046661B} = {5D196596-756D-45C2-8A05-C8E4AB8A36E6} {4D078B2A-029B-4B53-8FA9-5D2F9330BC72} = {65FA7B8E-7D2E-41D1-9740-BBB7D8B8ABE3} {0E10A5EB-7E48-408A-A4E7-8AB2A7AF143A} = {65FA7B8E-7D2E-41D1-9740-BBB7D8B8ABE3} EndGlobalSection diff --git a/src/dotnetCampus.Ipc.Abstractions/Context/IPeerConnectionBrokenArgs.cs b/src/dotnetCampus.Ipc.Abstractions/Context/IPeerConnectionBrokenArgs.cs deleted file mode 100644 index 0437de23..00000000 --- a/src/dotnetCampus.Ipc.Abstractions/Context/IPeerConnectionBrokenArgs.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace dotnetCampus.Ipc.Abstractions.Context -{ - /// - /// 对方连接断开事件参数 - /// - public interface IPeerConnectionBrokenArgs - { - - } -} diff --git a/src/dotnetCampus.Ipc.Abstractions/Context/IpcBufferMessage.cs b/src/dotnetCampus.Ipc.Abstractions/Context/IpcBufferMessage.cs deleted file mode 100644 index 273fe019..00000000 --- a/src/dotnetCampus.Ipc.Abstractions/Context/IpcBufferMessage.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Diagnostics; - -namespace dotnetCampus.Ipc.Abstractions.Context -{ - /// - /// 表示一段 Ipc 消息内容 - /// - public readonly struct IpcBufferMessage - { - /// - /// 创建一段 Ipc 消息内容 - /// - /// - [DebuggerStepThrough] - public IpcBufferMessage(byte[] buffer) - { - Buffer = buffer; - Start = 0; - Count = buffer.Length; - } - - /// - /// 创建一段 Ipc 消息内容 - /// - /// - /// - /// - [DebuggerStepThrough] - public IpcBufferMessage(byte[] buffer, int start, int count) - { - Buffer = buffer; - Start = start; - Count = count; - } - - /// - /// 缓存数据 - /// - public byte[] Buffer { get; } - - /// - /// 缓存数据的起始点 - /// - public int Start { get; } - - /// - /// 数据长度 - /// - public int Count { get; } - } -} diff --git a/src/dotnetCampus.Ipc.Abstractions/IMessageWriter.cs b/src/dotnetCampus.Ipc.Abstractions/IMessageWriter.cs deleted file mode 100644 index cb71a400..00000000 --- a/src/dotnetCampus.Ipc.Abstractions/IMessageWriter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -namespace dotnetCampus.Ipc.Abstractions -{ - /// - /// 用于表示发送消息 - /// - public interface IMessageWriter - { - /// - /// 向服务端发送消息 - /// - /// - /// - /// - /// 这一次写入的是什么内容,用于调试 - /// - Task WriteMessageAsync(byte[] buffer, int offset, int count, - [CallerMemberName] string summary = null!); - } -} diff --git a/src/dotnetCampus.Ipc.Abstractions/IpcMessageWriter.cs b/src/dotnetCampus.Ipc.Abstractions/IpcMessageWriter.cs deleted file mode 100644 index 97875b6c..00000000 --- a/src/dotnetCampus.Ipc.Abstractions/IpcMessageWriter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; - -namespace dotnetCampus.Ipc.Abstractions -{ - /// - /// 提供消息的写入方法 - /// - public class IpcMessageWriter : IMessageWriter - { - /// - /// 创建提供消息的写入方法 - /// - /// 实际用来写入的方法 - public IpcMessageWriter(IMessageWriter messageWriter) - { - MessageWriter = messageWriter; - } - - /// - public Task WriteMessageAsync(byte[] buffer, int offset, int count, [CallerMemberName] string summary = null!) - { - return MessageWriter.WriteMessageAsync(buffer, offset, count, summary); - } - - /// - /// 向对方发送消息 - /// - /// 字符串消息,将会被使用Utf-8编码转换然后发送 - /// - /// - public Task WriteMessageAsync(string message, string? summary = null) - { - if (summary is null) - { - summary = message; - } - - var messageBuffer = Encoding.UTF8.GetBytes(message); - return WriteMessageAsync(messageBuffer, 0, messageBuffer.Length, summary); - } - - private IMessageWriter MessageWriter { get; } - } -} diff --git a/src/dotnetCampus.Ipc.Abstractions/IpcRequestMessage.cs b/src/dotnetCampus.Ipc.Abstractions/IpcRequestMessage.cs deleted file mode 100644 index cab0b001..00000000 --- a/src/dotnetCampus.Ipc.Abstractions/IpcRequestMessage.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Diagnostics; -using dotnetCampus.Ipc.Abstractions.Context; - -namespace dotnetCampus.Ipc.Abstractions -{ - /// - /// 表示一条 IPC 请求消息 - /// - public readonly struct IpcRequestMessage - { - /// - /// 创建 IPC 请求消息 - /// - /// - /// - [DebuggerStepThrough] - public IpcRequestMessage(string summary, IpcBufferMessage requestMessage) - { - Summary = summary; - RequestMessage = requestMessage; - } - - /// - /// 创建 IPC 请求消息 - /// - [DebuggerStepThrough] - public IpcRequestMessage(string summary, byte[] buffer) : this(summary, new IpcBufferMessage(buffer)) - { - } - - /// - /// 消息的调试友好文本,用来描述这条消息是什么 - /// - public string Summary { get; } - - /// - /// 请求的信息 - /// - public IpcBufferMessage RequestMessage { get; } - } -} diff --git a/src/dotnetCampus.Ipc.Abstractions/dotnetCampus.Ipc.Abstractions.csproj b/src/dotnetCampus.Ipc.Abstractions/dotnetCampus.Ipc.Abstractions.csproj deleted file mode 100644 index 086ddb37..00000000 --- a/src/dotnetCampus.Ipc.Abstractions/dotnetCampus.Ipc.Abstractions.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - netstandard2.0;net45; - enable - true - - - - - - - true - - - true - - - - - - true - - - - true - snupkg - - - - - - - - - - diff --git a/src/dotnetCampus.Ipc.Abstractions/dotnetCampus.Ipc.Abstractions.csproj.DotSettings b/src/dotnetCampus.Ipc.Abstractions/dotnetCampus.Ipc.Abstractions.csproj.DotSettings deleted file mode 100644 index 26203eb1..00000000 --- a/src/dotnetCampus.Ipc.Abstractions/dotnetCampus.Ipc.Abstractions.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/EmptyIpcRequestHandler.cs b/src/dotnetCampus.Ipc.PipeCore/Context/EmptyIpcRequestHandler.cs deleted file mode 100644 index d74f5f36..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Context/EmptyIpcRequestHandler.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Abstractions.Context; - -namespace dotnetCampus.Ipc.PipeCore.Context -{ - class EmptyIpcRequestHandler : IIpcRequestHandler - { - public Task HandleRequestMessage(IIpcRequestContext requestContext) - { - // 我又不知道业务,不知道怎么玩…… - var responseMessage = new IpcRequestMessage(nameof(EmptyIpcRequestHandler), new IpcBufferMessage(new byte[0])); - return Task.FromResult((IIpcHandleRequestMessageResult) new IpcHandleRequestMessageResult(responseMessage)); - } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/IpcHandleRequestMessageResult.cs b/src/dotnetCampus.Ipc.PipeCore/Context/IpcHandleRequestMessageResult.cs deleted file mode 100644 index ac15a95d..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Context/IpcHandleRequestMessageResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Diagnostics; -using dotnetCampus.Ipc.Abstractions; - -namespace dotnetCampus.Ipc.PipeCore.Context -{ - class IpcHandleRequestMessageResult : IIpcHandleRequestMessageResult - { - [DebuggerStepThrough] - public IpcHandleRequestMessageResult(IpcRequestMessage returnMessage) - { - ReturnMessage = returnMessage; - } - - public IpcRequestMessage ReturnMessage { get; } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageCommandType.cs b/src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageCommandType.cs deleted file mode 100644 index 5796109f..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageCommandType.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace dotnetCampus.Ipc.PipeCore.Context -{ - /// - /// 用于作为命令类型,用于框架的命令和业务的命令 - /// - public enum IpcMessageCommandType : short - { - /// - /// 向对方服务器注册 - /// - PeerRegister = -1, - - /* - /// - /// 发送回复信息 - /// - SendAck = 0B0010, - - /// - /// 发送回复信息,同时向对方服务器注册 - /// - SendAckAndRegisterToPeer = PeerRegister | SendAck, - */ - - /// - /// 业务层的消息 - /// - /// 所有大于 0 的都是业务层消息 - Business = 1, - - /// - /// 请求信息,这也是业务层消息 - /// - RequestMessage = 1 << 1 | Business, - - /// - /// 回应信息,这也是业务层消息 - /// - ResponseMessage = 1 << 2 | Business, - - /// - /// 其他信息 - /// - Unknown = short.MaxValue, - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/Logger_/ILogger.cs b/src/dotnetCampus.Ipc.PipeCore/Context/Logger_/ILogger.cs deleted file mode 100644 index 62b78f42..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Context/Logger_/ILogger.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace dotnetCampus.Ipc.PipeCore.Context -{ - /// - /// 日志 - /// - /// 因为 dotnetCampus.Logging 库还没写完,因此这里没有任何内容 - public interface ILogger - { - - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/Logger_/IpcLogger.cs b/src/dotnetCampus.Ipc.PipeCore/Context/Logger_/IpcLogger.cs deleted file mode 100644 index fb45ca52..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Context/Logger_/IpcLogger.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace dotnetCampus.Ipc.PipeCore.Context -{ - class IpcLogger : ILogger - { - public IpcLogger(IpcContext ipcContext) - { - IpcContext = ipcContext; - } - - public override string ToString() - { - return $"[{IpcContext.PipeName}]"; - } - - private IpcContext IpcContext { get; } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/IClientMessageWriter.cs b/src/dotnetCampus.Ipc.PipeCore/Core_/IClientMessageWriter.cs deleted file mode 100644 index 2b5a2653..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/IClientMessageWriter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.PipeCore.Context; - -namespace dotnetCampus.Ipc.PipeCore -{ - internal interface IClientMessageWriter : IMessageWriter - { - Task WriteMessageAsync(in IpcBufferMessageContext ipcBufferMessageContext); - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/Peer_/PeerProxy.cs b/src/dotnetCampus.Ipc.PipeCore/Core_/Peer_/PeerProxy.cs deleted file mode 100644 index 960fcd25..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/Peer_/PeerProxy.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.IpcPipe; -using dotnetCampus.Ipc.PipeCore.Utils; - -namespace dotnetCampus.Ipc.PipeCore -{ - /// - /// 用于表示远程的对方 - /// - public class PeerProxy : IPeerProxy - { - internal PeerProxy(string peerName, IpcClientService ipcClientService, IpcContext ipcContext) - { - PeerName = peerName; - IpcClientService = ipcClientService; - IpcMessageWriter = new IpcMessageWriter(ipcClientService); - - IpcContext = ipcContext; - - IpcMessageRequestManager = new IpcMessageRequestManager(); - IpcMessageRequestManager.OnIpcClientRequestReceived += ResponseManager_OnIpcClientRequestReceived; - } - - internal PeerProxy(string peerName, IpcClientService ipcClientService, IpcInternalPeerConnectedArgs ipcInternalPeerConnectedArgs, IpcContext ipcContext) : - this(peerName, ipcClientService, ipcContext) - { - Update(ipcInternalPeerConnectedArgs); - } - - /// - /// 对方的服务器名 - /// - public string PeerName { get; } - - internal TaskCompletionSource WaitForFinishedTaskCompletionSource { get; } = - new TaskCompletionSource(); - - private IpcContext IpcContext { get; } - - /// - /// 当收到消息时触发 - /// - public event EventHandler? MessageReceived; - - internal IpcMessageRequestManager IpcMessageRequestManager { get; } - - /// - public async Task GetResponseAsync(IpcRequestMessage request) - { - var ipcClientRequestMessage = IpcMessageRequestManager.CreateRequestMessage(request); - await IpcClientService.WriteMessageAsync(ipcClientRequestMessage.IpcBufferMessageContext); - return await ipcClientRequestMessage.Task; - } - - /// - public event EventHandler? PeerConnectionBroken; - - /// - /// 用于写入数据 - /// - public IpcMessageWriter IpcMessageWriter { get; } - - /// - /// 表示作为客户端和对方连接 - /// - /// 框架内使用 - internal IpcClientService IpcClientService { get; } - - internal bool IsBroken { get; private set; } - - /// - /// 获取是否连接完成,也就是可以读取,可以发送 - /// - public bool IsConnectedFinished { get; private set; } - - ///// - ///// 当断开连接的时候触发 - ///// - //public event EventHandler? Disconnected; - - internal void Update(IpcInternalPeerConnectedArgs ipcInternalPeerConnectedArgs) - { - Debug.Assert(ipcInternalPeerConnectedArgs.PeerName == PeerName); - - var serverStreamMessageReader = ipcInternalPeerConnectedArgs.ServerStreamMessageReader; - - serverStreamMessageReader.MessageReceived -= ServerStreamMessageReader_MessageReceived; - serverStreamMessageReader.MessageReceived += ServerStreamMessageReader_MessageReceived; - - // 连接断开 - serverStreamMessageReader.PeerConnectBroke -= ServerStreamMessageReader_PeerConnectBroke; - serverStreamMessageReader.PeerConnectBroke += ServerStreamMessageReader_PeerConnectBroke; - - IsConnectedFinished = true; - - if (WaitForFinishedTaskCompletionSource.TrySetResult(true)) - { - } - else - { - Debug.Assert(false, "重复调用"); - } - } - - private void ServerStreamMessageReader_PeerConnectBroke(object? sender, PeerConnectionBrokenArgs e) - { - OnPeerConnectionBroken(e); - } - - private void ServerStreamMessageReader_MessageReceived(object? sender, PeerMessageArgs e) - { - IpcMessageRequestManager.OnReceiveMessage(e); - - MessageReceived?.Invoke(sender, e); - } - - private void ResponseManager_OnIpcClientRequestReceived(object? sender, IpcClientRequestArgs e) - { - var ipcRequestHandlerProvider = IpcContext.IpcRequestHandlerProvider; - ipcRequestHandlerProvider.HandleRequest(this, e); - } - - private void OnPeerConnectionBroken(IPeerConnectionBrokenArgs e) - { - IsBroken = true; - IpcClientService.Dispose(); - - PeerConnectionBroken?.Invoke(this, e); - } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Ipc.csproj.DotSettings b/src/dotnetCampus.Ipc.PipeCore/Ipc.csproj.DotSettings deleted file mode 100644 index 42912085..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Ipc.csproj.DotSettings +++ /dev/null @@ -1,4 +0,0 @@ - - True - True - True \ No newline at end of file diff --git a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcRequestMessageContext.cs b/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcRequestMessageContext.cs deleted file mode 100644 index 4b30a787..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcRequestMessageContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Diagnostics; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore.Context; - -namespace dotnetCampus.Ipc.PipeCore.IpcPipe -{ - class IpcRequestMessageContext : IIpcRequestContext - { - [DebuggerStepThrough] - public IpcRequestMessageContext(IpcBufferMessage ipcBufferMessage, IPeerProxy peer) - { - IpcBufferMessage = ipcBufferMessage; - Peer = peer; - } - - /// - public bool Handle { get; set; } - - /// - public IpcBufferMessage IpcBufferMessage { get; } - - public IPeerProxy Peer { get; } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageRequestManager.cs b/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageRequestManager.cs deleted file mode 100644 index 672d4e42..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageRequestManager.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore.Context; - -namespace dotnetCampus.Ipc.PipeCore.IpcPipe -{ - /// - /// 请求管理 - /// - /// 这是用来期待发送一条消息之后,在对方的业务层能有回复的消息 - /// 这是服务器-客户端模型 - /// 客户端向服务器发起请求,服务器端业务层处理之后返回响应信息 - /// 通过调用 方法创建出请求消息 - /// 然后将此消息的 通过现有 发送到服务器端。同时客户端可以使用 进行等待 - /// 服务器端接收到 的内容,将会在 事件触发,这个事件将会带上 参数 - /// 在服务器端处理完成之后,底层的方法是通过调用 方法创建响应消息,通过 发送给客户端 - /// 客户端收到了服务器端的响应信息,将会释放 任务,客户端从 可以拿到服务器端的返回值 - /// - class IpcMessageRequestManager : IpcMessageManagerBase - { - public IpcClientRequestMessage CreateRequestMessage(IpcRequestMessage request) - { - ulong currentMessageId; - var task = new TaskCompletionSource(); - lock (Locker) - { - currentMessageId = CurrentMessageId; - // 在超过 ulong.MaxValue 之后,将会是 0 这个值 - CurrentMessageId++; - - TaskList[currentMessageId] = task; - } - - var requestMessage = CreateRequestMessageInner(request, currentMessageId); - return new IpcClientRequestMessage(requestMessage, task.Task, new IpcClientRequestMessageId(currentMessageId)); - } - - private Dictionary> TaskList { get; } = - new Dictionary>(); - - /// - /// 收到消息,包括收到别人的请求消息,和收到别人的响应消息 - /// - /// - public void OnReceiveMessage(PeerMessageArgs args) - { - HandleResponse(args); - if (args.Handle) - { - return; - } - - HandleRequest(args); - } - - private void HandleRequest(PeerMessageArgs args) - { - if (!args.MessageCommandType.HasFlag(IpcMessageCommandType.RequestMessage)) - { - // 如果没有命令标识,那么返回 - return; - } - - Stream message = args.Message; - if (message.Length < RequestMessageHeader.Length + sizeof(ulong)) - { - return; - } - - var currentPosition = message.Position; - try - { - if (CheckRequestHeader(message)) - { - // 标记在这一级消费 - args.SetHandle(message: nameof(HandleRequest)); - - var ipcClientRequestArgs = ParseRequestMessage(message); - - OnIpcClientRequestReceived?.Invoke(this, ipcClientRequestArgs); - } - } - finally - { - message.Position = currentPosition; - } - } - - private static IpcClientRequestArgs ParseRequestMessage(Stream message) - { - var binaryReader = new BinaryReader(message); - var messageId = binaryReader.ReadUInt64(); - var requestMessageLength = binaryReader.ReadInt32(); - var requestMessageByteList = binaryReader.ReadBytes(requestMessageLength); - var ipcClientRequestArgs = - new IpcClientRequestArgs(new IpcClientRequestMessageId(messageId), - new IpcBufferMessage(requestMessageByteList)); - return ipcClientRequestArgs; - } - - public event EventHandler? OnIpcClientRequestReceived; - - private void HandleResponse(PeerMessageArgs args) - { - if (!args.MessageCommandType.HasFlag(IpcMessageCommandType.ResponseMessage)) - { - // 如果没有命令标识,那么返回 - return; - } - - Stream message = args.Message; - - if (message.Length < ResponseMessageHeader.Length + sizeof(ulong)) - { - return; - } - - var currentPosition = message.Position; - try - { - if (CheckResponseHeader(message)) - { - // 标记在这一级消费 - args.SetHandle(message: nameof(HandleResponse)); - - var binaryReader = new BinaryReader(message); - var messageId = binaryReader.ReadUInt64(); - TaskCompletionSource? task = null; - lock (Locker) - { - if (TaskList.TryGetValue(messageId, out task)) - { - } - else - { - return; - } - } - - if (task == null) - { - return; - } - - var responseMessageLength = binaryReader.ReadInt32(); - var responseMessageByteList = binaryReader.ReadBytes(responseMessageLength); - task.SetResult(new IpcBufferMessage(responseMessageByteList)); - } - } - finally - { - message.Position = currentPosition; - } - } - - - private object Locker => TaskList; - - private ulong CurrentMessageId { set; get; } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageResponseManager.cs b/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageResponseManager.cs deleted file mode 100644 index e329c857..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageResponseManager.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.PipeCore.Context; - -namespace dotnetCampus.Ipc.PipeCore.IpcPipe -{ - class IpcMessageResponseManager : IpcMessageManagerBase - { - public IpcBufferMessageContext CreateResponseMessage(IpcClientRequestMessageId messageId, - IpcRequestMessage response) - => CreateResponseMessageInner(messageId, response); - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcRequestHandlerProvider.cs b/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcRequestHandlerProvider.cs deleted file mode 100644 index 2f51ed96..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcRequestHandlerProvider.cs +++ /dev/null @@ -1,49 +0,0 @@ -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.PipeCore.Context; - -namespace dotnetCampus.Ipc.PipeCore.IpcPipe -{ - /// - /// 关联 的联系 - /// - class IpcRequestHandlerProvider - { - public IpcRequestHandlerProvider(IpcContext ipcContext) - { - IpcContext = ipcContext; - } - - public IpcContext IpcContext { get; } - - /// - /// 处理请求消息 - /// - /// - /// - /// 有三步 - /// 1. 取出消息和上下文里面带的 用于处理消息 - /// 2. 构建出 传入到 处理 - /// 3. 将 的返回值发送给到客户端 - public async void HandleRequest(PeerProxy sender, IpcClientRequestArgs args) - { - var requestMessage = args.IpcBufferMessage; - var peerProxy = sender; - - var ipcRequestContext = new IpcRequestMessageContext(requestMessage, peerProxy); - - // 处理消息 - // 优先从 Peer 里面找处理的方法,这样上层可以对某个特定的 Peer 做不同的处理 - // Todo 需要设计这部分 API 现在因为没有 API 的设计,先全部走 DefaultIpcRequestHandler 的逻辑 - IIpcRequestHandler ipcRequestHandler = IpcContext.IpcConfiguration.DefaultIpcRequestHandler; - var result = await ipcRequestHandler.HandleRequestMessage(ipcRequestContext); - - // 构建信息回复 - var responseManager = IpcContext.IpcMessageResponseManager; - var responseMessage = responseManager.CreateResponseMessage(args.MessageId, result.ReturnMessage); - - // 发送回客户端 - await peerProxy.IpcClientService.WriteMessageAsync(responseMessage); - } - } -} - diff --git a/src/dotnetCampus.Ipc.PipeCore/Pipes/IpcRequestHandlerProvider.cs b/src/dotnetCampus.Ipc.PipeCore/Pipes/IpcRequestHandlerProvider.cs new file mode 100644 index 00000000..e02abfc9 --- /dev/null +++ b/src/dotnetCampus.Ipc.PipeCore/Pipes/IpcRequestHandlerProvider.cs @@ -0,0 +1 @@ + diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/AsyncBinaryReader.cs b/src/dotnetCampus.Ipc.PipeCore/Utils/AsyncBinaryReader.cs deleted file mode 100644 index 5e7edfec..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/AsyncBinaryReader.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; - -namespace dotnetCampus.Ipc.PipeCore.Utils -{ - class AsyncBinaryReader - { - public AsyncBinaryReader(Stream stream) - { - Stream = stream; - } - - private Stream Stream { get; } - - public async Task ReadUInt16Async() - { - var byteList = await InternalReadAsync(2); - return BitConverter.ToUInt16(byteList, 0); - } - - public async Task ReadReadUInt64Async() - { - var byteList = await InternalReadAsync(sizeof(UInt64)); - return BitConverter.ToUInt64(byteList, 0); - } - - public async Task ReadUInt32Async() - { - var byteList = await InternalReadAsync(sizeof(UInt32)); - return BitConverter.ToUInt32(byteList, 0); - } - - private async Task InternalReadAsync(int numBytes) - { - var byteList = new byte[numBytes]; - var bytesRead = 0; - - do - { - var n = await Stream.ReadAsync(byteList, bytesRead, numBytes - bytesRead).ConfigureAwait(false); - if (n == 0) - { - throw new EndOfStreamException(); - } - - bytesRead += n; - } while (bytesRead < numBytes); - - return byteList; - } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/DoubleBufferTaskExtension.cs b/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/DoubleBufferTaskExtension.cs deleted file mode 100644 index 0dc96041..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/DoubleBufferTaskExtension.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading.Tasks; -using dotnetCampus.Threading; - -namespace dotnetCampus.Ipc.PipeCore.Utils.Extensions -{ - static class DoubleBufferTaskExtension - { - public static async Task AddTaskAsync(this DoubleBufferTask> doubleBufferTask, Func task) - { - var taskCompletionSource = new TaskCompletionSource(); - - doubleBufferTask.AddTask(async () => - { - try - { - await task(); - taskCompletionSource.SetResult(true); - } - catch (Exception e) - { - taskCompletionSource.SetException(e); - } - }); - - await taskCompletionSource.Task; - } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/IpcBufferMessageExtension.cs b/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/IpcBufferMessageExtension.cs deleted file mode 100644 index 97cce90b..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/IpcBufferMessageExtension.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using dotnetCampus.Ipc.Abstractions.Context; - -namespace dotnetCampus.Ipc.PipeCore.Utils.Extensions -{ - static class IpcBufferMessageExtension - { -#if NETCOREAPP3_1_OR_GREATER - public static Span AsSpan(this in IpcBufferMessage message) - { - return message.Buffer.AsSpan(message.Start, message.Count); - } -#endif - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/LoggerExtension.cs b/src/dotnetCampus.Ipc.PipeCore/Utils/LoggerExtension.cs deleted file mode 100644 index aebc52d5..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/LoggerExtension.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using dotnetCampus.Ipc.PipeCore.Context; - -namespace dotnetCampus.Ipc.PipeCore.Utils -{ - internal static class LoggerExtension - { - public static void Debug(this ILogger logger, string message) - { - Console.WriteLine(logger?.ToString() + message); - } - - public static void Trace(this ILogger logger, string message) - { - Console.WriteLine(message); - } - - public static void Error(this ILogger logger, string message) - { - Console.WriteLine(message); - } - - public static void Error(this ILogger logger, Exception exception) - { - Console.WriteLine(exception); - } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/dotnetCampus.Ipc.PipeCore.csproj b/src/dotnetCampus.Ipc.PipeCore/dotnetCampus.Ipc.PipeCore.csproj deleted file mode 100644 index 116d1460..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/dotnetCampus.Ipc.PipeCore.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - netcoreapp3.1;net45 - enable - true - - - - - - - - - - - true - - - true - - - - - - true - - - - true - snupkg - - - - - - - - - - - diff --git a/src/dotnetCampus.Ipc.PipeCore/dotnetCampus.Ipc.PipeCore.csproj.DotSettings b/src/dotnetCampus.Ipc.PipeCore/dotnetCampus.Ipc.PipeCore.csproj.DotSettings deleted file mode 100644 index 0c937c08..00000000 --- a/src/dotnetCampus.Ipc.PipeCore/dotnetCampus.Ipc.PipeCore.csproj.DotSettings +++ /dev/null @@ -1,8 +0,0 @@ - - True - True - True - True - True - True - True \ No newline at end of file diff --git a/src/dotnetCampus.Ipc/CompilerServices/Attributes/IpcPublicAttribute.cs b/src/dotnetCampus.Ipc/CompilerServices/Attributes/IpcPublicAttribute.cs new file mode 100644 index 00000000..004b15a5 --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/Attributes/IpcPublicAttribute.cs @@ -0,0 +1,47 @@ +using System; + +namespace dotnetCampus.Ipc.CompilerServices.Attributes +{ + /// + /// 指示此类型在 IPC 中公开,可被其他进程发现并使用。 + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + public class IpcPublicAttribute : Attribute + { + /// + /// 指示此类型在 IPC 中公开,可被其他进程发现并使用。 + /// + /// + /// 此类型对 IPC 公开时,应以此契约类型公开。 + /// 其他希望访问此 IPC 公开类型的进程能通过此契约类型找到并使用此类型的实例。 + /// + /// + /// 生成一个 IPC 代理类。当需要使用远端的此类型的对象时,应通过此代理类来访问。名字可以随意指定,会在编译时自动生成对应名字的代理类。 + /// + /// + /// 生成一个 IPC 对接类。当其他进程试图访问此类型时,会通过此对接类来间接访问。名字可以随意指定,会在编译时自动生成对应名字的对接类。 + /// + public IpcPublicAttribute(Type contractType, Type userSideProxyType, Type ownerSideJointType) + { + ContractType = contractType; + ProxyType = userSideProxyType; + JointType = ownerSideJointType; + } + + /// + /// 此类型对 IPC 公开时,应以此契约类型公开。 + /// 其他希望访问此 IPC 公开类型的进程能通过此契约类型找到并使用此类型的实例。 + /// + public Type ContractType { get; } + + /// + /// IPC 代理类。当需要使用远端的此类型的对象时,应通过此代理类来访问。名字可以随意指定,会在编译时自动生成对应名字的代理类。 + /// + public Type ProxyType { get; } + + /// + /// IPC 对接类。当其他进程试图访问此类型时,会通过此对接类来间接访问。名字可以随意指定,会在编译时自动生成对应名字的对接类。 + /// + public Type JointType { get; } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/Attributes/IpcReadonlyAttribute.cs b/src/dotnetCampus.Ipc/CompilerServices/Attributes/IpcReadonlyAttribute.cs new file mode 100644 index 00000000..8a0d849d --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/Attributes/IpcReadonlyAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace dotnetCampus.Ipc.CompilerServices.Attributes +{ + /// + /// 在标记了 的类内部,标记一个属性是只读的。 + /// 当通过 IPC 访问过一次这个属性后,此属性不再变化,后续无需再通过 IPC 读取,可直接使用本地缓存的值。 + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)] + public sealed class IpcReadonlyAttribute : Attribute + { + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Contexts/GeneratedIpcJointResponse.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Contexts/GeneratedIpcJointResponse.cs new file mode 100644 index 00000000..b383f2e1 --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Contexts/GeneratedIpcJointResponse.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Contexts +{ + internal class GeneratedIpcJointResponse : IIpcResponseMessage + { + internal static GeneratedIpcJointResponse Empty { get; } = new GeneratedIpcJointResponse(); + + private GeneratedIpcJointResponse() + { + } + + private GeneratedIpcJointResponse(IpcMessage message) + { + ResponseMessage = message; + } + + public IpcMessage ResponseMessage { get; } + + internal static async Task FromAsyncReturnModel(Task asyncReturnModel) + { + var returnModel = await asyncReturnModel.ConfigureAwait(false); + var message = returnModel is null + ? new IpcMessage() + : GeneratedProxyMemberReturnModel.Serialize(returnModel); + return new GeneratedIpcJointResponse(message); + } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcFactory.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcFactory.cs new file mode 100644 index 00000000..01867f35 --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcFactory.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +using dotnetCampus.Ipc.Exceptions; +using dotnetCampus.Ipc.Pipes; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + /// + /// 此工厂包含 库中对 的上下文扩展, + /// 因此可基于此上下文创建仅限于此 库的功能类,包括: + /// + /// + /// + /// + /// + /// + public static class GeneratedIpcFactory + { + /// + /// 创建用于通过 IPC 访问其他端 类型的代理对象,而此代理对象的类型为 。 + /// + /// IPC 对象的契约类型。 + /// IPC 代理对象的类型。 + /// 关联的 。 + /// IPC 远端。 + /// 如果要调用的远端对象有多个实例,请设置此 Id 值以找到期望的实例。 + /// 契约类型。 + public static TContract CreateIpcProxy(this IIpcProvider ipcProvider, IPeerProxy peer, string? ipcObjectId = null) + where TContract : class + where TIpcProxy : GeneratedIpcProxy, TContract, new() => new TIpcProxy + { + Context = GetContext(ipcProvider), + PeerProxy = peer, + ObjectId = ipcObjectId + }; + + /// + /// 创建用于对接来自其他端通过 IPC 访问 类型的对接对象,而此对接对象的类型为 。 + /// + /// IPC 对象的契约类型。 + /// IPC 对接对象的类型。 + /// 关联的 。 + /// 真实的对象。 + /// 如果被对接的对象有多个实例,请设置此 Id 值以对接正确的实例。 + public static TContract CreateIpcJoint(this IIpcProvider ipcProvider, TContract realInstance, string? ipcObjectId = null) + where TContract : class + where TIpcJoint : GeneratedIpcJoint, new() + { + var joint = new TIpcJoint(); + joint.SetInstance(realInstance); + GetContext(ipcProvider).JointManager.AddPublicIpcObject(joint, ipcObjectId); + return realInstance; + } + + private static GeneratedProxyJointIpcContext GetContext(IIpcProvider ipcProvider) + => ipcProvider.IpcContext.GeneratedProxyJointIpcContext; + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcJoint.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcJoint.cs new file mode 100644 index 00000000..d38f890e --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcJoint.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models; + +using Newtonsoft.Json.Linq; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + /// + /// 为自动生成的 IPC 对接类提供基类。 + /// + public abstract class GeneratedIpcJoint + { + /// + /// 设置此对接对象的真实实例。 + /// + /// 真实实例。 + internal abstract void SetInstance(object realInstance); + internal abstract object? GetProperty(string propertyName); + internal abstract object? SetProperty(string propertyName, object? value); + internal abstract object? CallMethod(string methodName, object?[]? args); + internal abstract Task CallMethodAsync(string methodName, object?[]? args); + } + + /// + /// 为自动生成的 IPC 对接类提供基类。 + /// + /// 应该对接的契约类型(必须是一个接口)。 + public abstract class GeneratedIpcJoint : GeneratedIpcJoint where TContract : class + { + /// + /// 获取属性值的方法集合。 + /// + private readonly Dictionary> _propertyGetters = new(); + + /// + /// 设置属性值的方法集合。 + /// + private readonly Dictionary> _propertySetters = new(); + + /// + /// 调用方法的方法集合。 + /// + private readonly Dictionary<(string methodName, int parameterCount), Func> _methods = new(); + + /// + /// 调用异步方法的方法集合。 + /// + private readonly Dictionary<(string methodName, int parameterCount), Func>> _asyncMethods = new(); + + /// + /// 设置此对接对象的真实实例。 + /// + /// 真实实例。 + internal sealed override void SetInstance(object realInstance) => SetInstance((TContract) realInstance); + + /// + /// 设置此对接对象的真实实例。 + /// + /// 真实实例。 + internal void SetInstance(TContract realInstance) + { + _propertyGetters.Clear(); + _propertySetters.Clear(); + _methods.Clear(); + _asyncMethods.Clear(); + + MatchMembers(realInstance); + } + + /// + /// 派生类重写此方法时,通过调用一系列 MatchXxx 方法来将 接口中的所有成员与对接方法进行匹配。 + /// + /// 当对接时,可使用此参数来访问真实对象。 + protected abstract void MatchMembers(TContract realInstance); + + protected void MatchMethod(string methodName, Action methodInvoker) + { + _methods.Add((methodName, 0), _ => + { + methodInvoker(); + return null; + }); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _asyncMethods.Add((methodName, 0), async _ => + { + await methodInvoker().ConfigureAwait(false); + return null; + }); + } + + protected void MatchMethod(string methodName, Action methodInvoker) + { + _methods.Add((methodName, 1), args => + { + methodInvoker(Cast(args![0])!); + return null; + }); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _asyncMethods.Add((methodName, 1), async args => + { + await methodInvoker(Cast(args![0])!).ConfigureAwait(false); + return null; + }); + } + + protected void MatchMethod(string methodName, Action methodInvoker) + { + _methods.Add((methodName, 2), args => + { + methodInvoker(Cast(args![0])!, Cast(args![1])!); + return null; + }); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _asyncMethods.Add((methodName, 2), async args => + { + await methodInvoker(Cast(args![0])!, Cast(args![1])!).ConfigureAwait(false); + return null; + }); + } + + protected void MatchMethod(string methodName, Action methodInvoker) + { + _methods.Add((methodName, 3), args => + { + methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!); + return null; + }); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _asyncMethods.Add((methodName, 3), async args => + { + await methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!).ConfigureAwait(false); + return null; + }); + } + + protected void MatchMethod(string methodName, Action methodInvoker) + { + _methods.Add((methodName, 4), args => + { + methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!, Cast(args![3])!); + return null; + }); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _asyncMethods.Add((methodName, 4), async args => + { + await methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!, Cast(args![3])!).ConfigureAwait(false); + return null; + }); + } + + protected void MatchMethod(string methodName, Action methodInvoker) + { + _methods.Add((methodName, 5), args => + { + methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!, Cast(args![3])!, Cast(args![4])!); + return null; + }); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _asyncMethods.Add((methodName, 5), async args => + { + await methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!, Cast(args![3])!, Cast(args![4])!).ConfigureAwait(false); + return null; + }); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _methods.Add((methodName, 0), _ => methodInvoker()); + } + + protected void MatchMethod(string methodName, Func> methodInvoker) + { + _asyncMethods.Add((methodName, 0), async _ => await methodInvoker().ConfigureAwait(false)); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _methods.Add((methodName, 1), args => methodInvoker(Cast(args![0])!)); + } + + protected void MatchMethod(string methodName, Func> methodInvoker) + { + _asyncMethods.Add((methodName, 1), async args => await methodInvoker(Cast(args![0])!).ConfigureAwait(false)); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _methods.Add((methodName, 2), args => methodInvoker(Cast(args![0])!, Cast(args![1])!)); + } + + protected void MatchMethod(string methodName, Func> methodInvoker) + { + _asyncMethods.Add((methodName, 2), async args => await methodInvoker(Cast(args![0])!, Cast(args![1])!).ConfigureAwait(false)); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _methods.Add((methodName, 3), args => methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!)); + } + + protected void MatchMethod(string methodName, Func> methodInvoker) + { + _asyncMethods.Add((methodName, 3), async args => await methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!).ConfigureAwait(false)); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _methods.Add((methodName, 4), args => methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!, Cast(args![3])!)); + } + + protected void MatchMethod(string methodName, Func> methodInvoker) + { + _asyncMethods.Add((methodName, 4), async args => await methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!, Cast(args![3])!).ConfigureAwait(false)); + } + + protected void MatchMethod(string methodName, Func methodInvoker) + { + _methods.Add((methodName, 5), args => methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!, Cast(args![3])!, Cast(args![4])!)); + } + + protected void MatchMethod(string methodName, Func> methodInvoker) + { + _asyncMethods.Add((methodName, 5), async args => await methodInvoker(Cast(args![0])!, Cast(args![1])!, Cast(args![2])!, Cast(args![3])!, Cast(args![4])!).ConfigureAwait(false)); + } + + protected void MatchProperty(string propertyName, Func getter) + { + _propertyGetters.Add(propertyName, () => getter()); + } + + protected void MatchProperty(string propertyName, Func getter, Action setter) + { + _propertyGetters.Add(propertyName, () => getter()); + _propertySetters.Add(propertyName, value => setter(Cast(value)!)); + } + + internal sealed override object? GetProperty(string propertyName) + { + if (_propertyGetters.TryGetValue(propertyName, out var getter)) + { + return getter(); + } + throw new NotImplementedException($"无法对接 {typeof(TContract).FullName}.{propertyName} 属性,因为没有在 {GetType().FullName} 的 IPC 对接类中进行匹配。"); + } + + internal sealed override object? SetProperty(string propertyName, object? value) + { + if (_propertySetters.TryGetValue(propertyName, out var setter)) + { + setter(value); + return null; + } + throw new NotImplementedException($"无法对接 {typeof(TContract).FullName}.{propertyName} 属性,因为没有在 {GetType().FullName} 的 IPC 对接类中进行匹配。"); + } + + internal sealed override object? CallMethod(string methodName, object?[]? args) + { + var count = args is null ? 0 : args.Length; + if (_methods.TryGetValue((methodName, count), out var method)) + { + return method(args); + } + throw CreateMethodNotMatchException(methodName, count); + } + + internal sealed override async Task CallMethodAsync(string methodName, object?[]? args) + { + var count = args is null ? 0 : args.Length; + if (_asyncMethods.TryGetValue((methodName, count), out var asyncMethod)) + { + return await asyncMethod(args).ConfigureAwait(false); + } + throw CreateMethodNotMatchException(methodName, count); + } + + private T? Cast(object? arg) + { + if (arg is JValue jValue) + { + return KnownTypeConverter.ConvertBack(jValue); + } + return (T?) arg; + } + + private Exception CreateMethodNotMatchException(string methodName, int count) + { + var methodPair = _methods.FirstOrDefault(pair => pair.Key.methodName == methodName); + var asyncMethodPair = _asyncMethods.FirstOrDefault(pair => pair.Key.methodName == methodName); + if (methodPair.Key.parameterCount != count) + { + return new NotImplementedException($"无法对接 {typeof(TContract).FullName}.{methodName}({count} 个参数) 方法,在 {GetType().FullName} 中能找到的 IPC 对接类中最接近的是 {methodPair.Key.parameterCount} 个参数的重载。"); + } + else if (asyncMethodPair.Key.parameterCount != count) + { + return new NotImplementedException($"无法对接 {typeof(TContract).FullName}.{methodName}({count} 个参数) 方法,在 {GetType().FullName} 中能找到的 IPC 对接类中最接近的是 {asyncMethodPair.Key.parameterCount} 个参数的异步重载。"); + } + else + { + return new NotImplementedException($"无法对接 {typeof(TContract).FullName}.{methodName} 方法,因为没有在 {GetType().FullName} 的 IPC 对接类中进行匹配。"); + } + } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcProxy.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcProxy.cs new file mode 100644 index 00000000..b6c78f68 --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedIpcProxy.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.CompilerServices.Attributes; +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models; +using dotnetCampus.Ipc.Exceptions; +using dotnetCampus.Ipc.Messages; + +using Newtonsoft.Json.Linq; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + /// + /// 提供给自动生成的代理对象使用,以便能够生成通过 IPC 方式访问目标成员的能力。 + /// + public abstract class GeneratedIpcProxy + { + private GeneratedProxyJointIpcContext? _context; + private string? _typeName; + + /// + /// 提供基于 .NET 类型的 IPC 传输上下文信息。 + /// + internal GeneratedProxyJointIpcContext Context + { + get => _context ?? throw new IpcLocalException($"基于 .NET 类型的 IPC 传输机制应使用 {typeof(GeneratedIpcFactory)} 工厂类型来构造。"); + set => _context = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// 获取或设置目标 IPC 节点。 + /// 如果设定为 null,则所有请求将返回默认值。(未来可能运行在抛出异常和返回默认值之间进行选择。) + /// + public IPeerProxy? PeerProxy { get; set; } + + /// + /// 如果要调用的远端对象有多个实例,请设置此 Id 值以找到期望的实例。 + /// + public string TypeName + { + get => _typeName ?? throw new IpcLocalException($"基于 .NET 类型的 IPC 传输机制应使用 {typeof(GeneratedIpcFactory)} 工厂类型来构造。"); + internal set => _typeName = value; + } + + /// + /// 如果要调用的远端对象有多个实例,请设置此 Id 值以找到期望的实例。 + /// + public string? ObjectId { get; set; } + } + + /// + /// 提供给自动生成的代理对象使用,以便能够生成通过 IPC 方式访问目标成员的能力。 + /// + public abstract class GeneratedIpcProxy : GeneratedIpcProxy where TContract : class + { + private readonly ConcurrentDictionary _readonlyPropertyValues = new(StringComparer.Ordinal); + + /// + /// 创建 类型的 IPC 代理对象。 + /// + protected GeneratedIpcProxy() + { + TypeName = typeof(TContract).FullName!; + } + + /// + /// 通过 IPC 访问目标对象上某属性的值。 + /// + /// 属性类型。 + /// 属性名称。 + /// 可异步等待的属性的值。 + protected async Task GetValueAsync([CallerMemberName] string propertyName = "") + { + return await IpcInvokeAsync(MemberInvokingType.GetProperty, propertyName, null).ConfigureAwait(false); + } + + /// + /// 通过 IPC 访问目标对象上某标记了 的属性。如果曾访问过,会将这个值缓存下来供下次无 IPC 访问。 + /// 当发生并发时,可能导致多次通过 IPC 访问此属性的值,但此方法依然是线程安全的。 + /// + /// 属性类型。 + /// 属性名称。 + /// 可异步等待的属性的值。 + protected async Task GetReadonlyValueAsync([CallerMemberName] string propertyName = "") + { + if (_readonlyPropertyValues.TryGetValue(propertyName, out var cachedValue)) + { + // 当只读字典中存在此属性的缓存时,直接取缓存。 + return (T?) cachedValue; + } + // 否则,通过 IPC 访问获取此属性的值后设入缓存。(这里可能存在并发情况,会导致浪费的 IPC 访问,但能确保数据一致性)。 + var value = await IpcInvokeAsync(MemberInvokingType.GetProperty, propertyName, null).ConfigureAwait(false); + _readonlyPropertyValues.TryAdd(propertyName, value); + return value; + } + + /// + /// 通过 IPC 设置目标对象上某属性的值。 + /// + /// 属性类型。 + /// 要设置的属性的值。 + /// 属性名称。 + /// 可异步等待的属性设置。 + protected async Task SetValueAsync(T value, [CallerMemberName] string propertyName = "") + { + await IpcInvokeAsync(MemberInvokingType.SetProperty, propertyName, new object?[] { value }).ConfigureAwait(false); + } + + protected async Task CallMethod([CallerMemberName] string methodName = "") + { + await IpcInvokeAsync(MemberInvokingType.Method, methodName, new object?[0]).ConfigureAwait(false); + } + + protected async Task CallMethod([CallerMemberName] string methodName = "") + { + return await IpcInvokeAsync(MemberInvokingType.Method, methodName, new object?[0]).ConfigureAwait(false); + } + + protected async Task CallMethod(object?[]? args, [CallerMemberName] string methodName = "") + { + await IpcInvokeAsync(MemberInvokingType.Method, methodName, args).ConfigureAwait(false); + } + + protected async Task CallMethod(object?[]? args, [CallerMemberName] string methodName = "") + { + return await IpcInvokeAsync(MemberInvokingType.Method, methodName, args).ConfigureAwait(false); + } + + protected async Task CallMethodAsync([CallerMemberName] string methodName = "") + { + await IpcInvokeAsync(MemberInvokingType.AsyncMethod, methodName, new object?[0]).ConfigureAwait(false); + } + + protected async Task CallMethodAsync([CallerMemberName] string methodName = "") + { + return await IpcInvokeAsync(MemberInvokingType.AsyncMethod, methodName, new object?[0]).ConfigureAwait(false); + } + + protected async Task CallMethodAsync(object?[]? args, [CallerMemberName] string methodName = "") + { + await IpcInvokeAsync(MemberInvokingType.AsyncMethod, methodName, args).ConfigureAwait(false); + } + + protected async Task CallMethodAsync(object?[]? args, [CallerMemberName] string methodName = "") + { + return await IpcInvokeAsync(MemberInvokingType.AsyncMethod, methodName, args).ConfigureAwait(false); + } + + private async Task IpcInvokeAsync(MemberInvokingType callType, string memberName, object?[]? args) + { + if (PeerProxy is null) + { + return default; + } + + var returnModel = await IpcInvokeAsync(new GeneratedProxyMemberInvokeModel + { + Id = ObjectId, + ContractFullTypeName = TypeName, + CallType = callType, + MemberName = memberName, + Args = args?.Select(SerializeArg).ToArray(), + }).ConfigureAwait(false); + + if (returnModel is null) + { + // 如果远端返回 null,则本地代理返回 null。 + return default; + } + + if (returnModel.Exception is { } exceptionModel) + { + // 如果远端抛出了异常,则本地代理抛出相同的异常。 + exceptionModel.Throw(); + } + + if (returnModel.Return is { } model + && Context.TryCreateProxyFromSerializationInfo(PeerProxy, + model.AssemblyQualifiedName, model.Id, out var proxyInstance)) + { + // 如果远端返回 IPC 公开的对象,则本地获取此对象的代理并返回。 + return (T) proxyInstance; + } + + // 其他情况直接使用反序列化的值返回。 + return Cast(returnModel.Return?.Value); + } + + private async Task IpcInvokeAsync(GeneratedProxyMemberInvokeModel model) + { + if (PeerProxy is null) + { + return null; + } + + var requestMessage = GeneratedProxyMemberInvokeModel.Serialize(model); + requestMessage = new IpcMessage(requestMessage.Tag, requestMessage.Body, CoreMessageType.JsonObject); + var responseMessage = await PeerProxy.GetResponseAsync(requestMessage).ConfigureAwait(false); + if (GeneratedProxyMemberReturnModel.TryDeserialize(responseMessage, out var returnModel)) + { + return returnModel; + } + else + { + throw new NotSupportedException("请谨慎对待此异常!无法处理 IPC 代理调用的返回值。"); + } + } + + private T? Cast(object? arg) + { + if (arg is JValue jValue) + { + return KnownTypeConverter.ConvertBack(jValue); + } + return (T?) arg; + } + + private GeneratedProxyObjectModel? SerializeArg(object? arg) + { + if (PeerProxy is null) + { + return null; + } + + if (Context.TryCreateSerializationInfoFromIpcRealInstance(arg, out var objectId, out var assemblyQualifiedName)) + { + // 如果此参数是一个 IPC 对象。 + return new GeneratedProxyObjectModel + { + Id = objectId, + AssemblyQualifiedName = assemblyQualifiedName, + }; + } + else + { + // 如果此参数只是一个普通对象。 + return new GeneratedProxyObjectModel + { + Value = KnownTypeConverter.Convert(arg), + }; + } + } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedProxyJointIpcContext.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedProxyJointIpcContext.cs new file mode 100644 index 00000000..98cdce9b --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedProxyJointIpcContext.cs @@ -0,0 +1,30 @@ +using dotnetCampus.Ipc.Context; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + /// + /// 绑定到 后可提供基于 .NET 类型的 IPC 传输。 + /// + public class GeneratedProxyJointIpcContext + { + /// + /// 创建 的新实例。 + /// + /// + internal GeneratedProxyJointIpcContext(IpcContext ipcContext) + { + JointManager = new PublicIpcJointManager(this, ipcContext); + RequestHandler = new GeneratedProxyJointIpcRequestHandler(this, ipcContext); + } + + /// + /// 包含 IPC 对接的管理。 + /// + internal PublicIpcJointManager JointManager { get; } + + /// + /// 请将此属性赋值给 IpcConfiguration.DefaultIpcRequestHandler 以获得 .NET 类型的 IPC 传输访问能力。 + /// + public IIpcRequestHandler RequestHandler { get; } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedProxyJointIpcRequestHandler.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedProxyJointIpcRequestHandler.cs new file mode 100644 index 00000000..37fd1b5e --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/GeneratedProxyJointIpcRequestHandler.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + /// + /// 为生成的 提供默认的对 的对接。 + /// + internal sealed class GeneratedProxyJointIpcRequestHandler : IIpcRequestHandler + { + private readonly IpcContext _ipcContext; + + internal GeneratedProxyJointIpcContext Owner { get; } + + internal GeneratedProxyJointIpcRequestHandler(GeneratedProxyJointIpcContext context, IpcContext ipcContext) + { + Owner = context; + _ipcContext = ipcContext; + } + + Task IIpcRequestHandler.HandleRequest(IIpcRequestContext requestContext) + { + return _ipcContext.TaskPool.Run(async () => + { + if (Owner.JointManager.TryJoint(requestContext, out var responseTask)) + { + requestContext.Handled = true; + var response = await responseTask.ConfigureAwait(false); + return response; + } + + return KnownIpcResponseMessages.CannotHandle; + }, _ipcContext.Logger); + } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyExceptionModel.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyExceptionModel.cs new file mode 100644 index 00000000..31b201da --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyExceptionModel.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Runtime.ExceptionServices; +using System.Runtime.Serialization; + +using dotnetCampus.Ipc.Exceptions; +using dotnetCampus.Ipc.Utils; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + [DataContract] + internal class GeneratedProxyExceptionModel + { + public GeneratedProxyExceptionModel() + { + } + + public GeneratedProxyExceptionModel(Exception exception) + { + ExceptionType = exception.GetType().FullName; + Message = exception.Message; + StackTrace = exception.StackTrace; + ExtraInfo = null; + } + + [DataMember(Name = "t")] + public string? ExceptionType { get; set; } + + [DataMember(Name = "m")] + public string? Message { get; set; } + + [DataMember(Name = "s")] + public string? StackTrace { get; set; } + + [DataMember(Name = "x")] + public string? ExtraInfo { get; set; } + + public void Throw() + { + if (ExceptionType is { } typeName) + { + if (ExceptionRebuilders.TryGetValue(typeName, out var builder)) + { + var exception = builder(Message, ExtraInfo); + if (StackTrace is { } stackTrace) + { + var deserializedRemoteException = ExceptionHacker.ReplaceStackTrace(exception, StackTrace); + ExceptionDispatchInfo.Capture(deserializedRemoteException).Throw(); + } + else + { + ExceptionDispatchInfo.Capture(exception).Throw(); + } + } + else + { + throw new IpcRemoteException($"不支持的远端异常类型 {typeName}。如果是忘加了,请添加到 ExceptionRebuilders 属性中,否则请抛 IPC 自定义的异常。", StackTrace); + } + } + + throw new InvalidOperationException("无法抛出远端对象的异常,因为无法获知异常的类型。"); + } + + private static readonly Dictionary> ExceptionRebuilders = new(StringComparer.Ordinal) + { + { typeof(ArgumentException).FullName!, (m, e) => new ArgumentException(m) }, + { typeof(ArgumentNullException).FullName!, (m, e) => new ArgumentNullException(e, m) }, + { typeof(InvalidCastException).FullName!, (m, e) => new InvalidCastException(m) }, + { typeof(NotImplementedException).FullName!, (m, e) => new NotImplementedException(m) }, + { typeof(NullReferenceException).FullName!, (m, e) => new NullReferenceException(m) }, + { typeof(BadImageFormatException).FullName!, (m, e) => new BadImageFormatException(m) }, + }; + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyMemberInvokeModel.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyMemberInvokeModel.cs new file mode 100644 index 00000000..756d23bb --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyMemberInvokeModel.cs @@ -0,0 +1,140 @@ +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; +using System.Text; + +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Serialization; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + /// + /// IPC 传输过程中使用的内部模型,表示如何调用一个远端的方法。 + /// 目前为调试方便,暂使用 JSON 格式,易读。后续如果成为性能瓶颈,则可换成字节流。 + /// + [DataContract] + internal class GeneratedProxyMemberInvokeModel + { + [ContractPublicPropertyName(nameof(Id))] + private string? _id; + + /// + /// 远端对象 Id。 + /// 当同一个契约类型的对象存在多个时,则需要通过此 Id 进行区分。 + /// 空字符串("")和空值(null)是相同含义,允许设 null 值,但获取时永不为 null(会自动转换为空字符串)。 + /// + [DataMember(Name = "i")] + [AllowNull] + public string Id + { + get => _id ?? ""; + set => _id = value; + } + + /// + /// 远端对象的契约类型名称(含命名空间,不含 Token)。 + /// + [DataMember(Name = "t")] + public string? ContractFullTypeName { get; set; } + + /// + /// 调用的成员名称(属性名、方法名)。 + /// + [DataMember(Name = "m")] + public string? MemberName { get; set; } + + /// + /// 指定如何调用远端的对象。目前支持读属性、写属性、调用方法和调用异步方法。 + /// + [DataMember(Name = "c")] + public MemberInvokingType CallType { get; set; } + + /// + /// 参数列表。 + /// + [DataMember(Name = "a")] + public GeneratedProxyObjectModel?[]? Args { get; set; } + + /// + /// 将调用远端方法的内部模型序列化成可供跨进程传输的 IPC 消息。 + /// + /// + /// + public static IpcMessage Serialize(GeneratedProxyMemberInvokeModel model) + { + return JsonIpcMessageSerializer.Serialize(model.ToString(), model); + } + + /// + /// 尝试将跨进程传输过来的 IPC 消息反序列化成一个 IPC 方法调用内部模型。 + /// + /// IPC 消息。 + /// + /// + public static bool TryDeserialize(IpcMessage message, [NotNullWhen(true)] out GeneratedProxyMemberInvokeModel? model) + { + return JsonIpcMessageSerializer.TryDeserialize(message, out model); + } + + /// + /// 把方法调用过程以调用栈上一帧的方式表示出来。 + /// + /// + public override string ToString() + { + var isMethod = CallType is MemberInvokingType.Method or MemberInvokingType.AsyncMethod; + var builder = new StringBuilder(); + if (!string.IsNullOrEmpty(Id)) + { + builder.Append('[').Append(Id).Append(']'); + } + if (CallType is MemberInvokingType.AsyncMethod) + { + builder.Append("async "); + } + if (ContractFullTypeName is { } typeName) + { + builder.Append(typeName); + } + if (MemberName is { } memberName) + { + builder.Append('.'); + if (CallType is MemberInvokingType.GetProperty) + { + builder.Append("get_"); + } + else if (CallType is MemberInvokingType.SetProperty) + { + builder.Append("set_"); + } + builder.Append(memberName); + if (isMethod) + { + builder.Append('('); + } + else if (CallType is MemberInvokingType.SetProperty) + { + builder.Append('='); + } + } + if (Args is { } args) + { + for (var i = 0; i < args.Length; i++) + { + var arg = args[i]?.Value; + if (i != 0) + { + builder.Append(", "); + } + builder.Append(arg?.ToString() ?? "null"); + } + } + if (isMethod) + { + builder.Append(')'); + } + return builder.ToString(); + } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyMemberReturnModel.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyMemberReturnModel.cs new file mode 100644 index 00000000..c62a259f --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyMemberReturnModel.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; + +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Serialization; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + [DataContract] + internal class GeneratedProxyMemberReturnModel + { +#if DEBUG + [DataMember(Name = "i")] + public GeneratedProxyMemberInvokeModel? Invoking { get; set; } +#endif + + [DataMember(Name = "r")] + public GeneratedProxyObjectModel? Return { get; set; } + + [DataMember(Name = "e")] + public GeneratedProxyExceptionModel? Exception { get; set; } + + public GeneratedProxyMemberReturnModel() + { + } + + public GeneratedProxyMemberReturnModel(object? @return) + { + if (@return is null) + { + // 当返回的对象为 null 时,返回值直接设定为 null。 + Return = null; + } + else + { + // 当返回对象为其他类型时,将尝试进行序列化。 + var jValue = KnownTypeConverter.Convert(@return); + Return = new GeneratedProxyObjectModel + { + Value = jValue, + }; + } + } + + public GeneratedProxyMemberReturnModel(Exception exception) + { + if (exception is null) + { + throw new ArgumentNullException(nameof(exception)); + } + + Exception = new GeneratedProxyExceptionModel(exception); + } + + public static IpcMessage Serialize(GeneratedProxyMemberReturnModel model) + { + return JsonIpcMessageSerializer.Serialize("Return", model); + } + + public static bool TryDeserialize(IpcMessage message, [NotNullWhen(true)] out GeneratedProxyMemberReturnModel? model) + { + return JsonIpcMessageSerializer.TryDeserialize(message, out model); + } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyObjectModel.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyObjectModel.cs new file mode 100644 index 00000000..aa5b139a --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/GeneratedProxyObjectModel.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; + +using Newtonsoft.Json.Linq; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models +{ + [DataContract] + internal class GeneratedProxyObjectModel + { + [ContractPublicPropertyName(nameof(Id))] + private string? _id; + + /// + /// 远端对象 Id。 + /// 当同一个契约类型的对象存在多个时,则需要通过此 Id 进行区分。 + /// 空字符串("")和空值(null)是相同含义,允许设 null 值,但获取时永不为 null(会自动转换为空字符串)。 + /// + [DataMember(Name = "i")] + [AllowNull] + public string Id + { + get => _id ?? ""; + set => _id = value; + } + + /// + /// 远端对象的类型名称(含命名空间,不含 Token)。 + /// + [DataMember(Name = "t")] + public string? AssemblyQualifiedName { get; set; } + + [DataMember(Name = "v")] + public JValue? Value { get; set; } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/KnownTypeConverter.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/KnownTypeConverter.cs new file mode 100644 index 00000000..7ec55d4d --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/KnownTypeConverter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Newtonsoft.Json.Linq; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models +{ + internal static class KnownTypeConverter + { + private static readonly Dictionary serializer, Func deserializer)> KnownTypeConverters = new() + { + { + typeof(IntPtr), + (x => new JValue(((IntPtr) x).ToInt64()), x => new IntPtr(x.ToObject())) + }, + }; + + internal static JValue Convert(object? value) + { + if (value == null) + { + return JValue.CreateNull(); + } + + var type = value.GetType(); + if (KnownTypeConverters.TryGetValue(type, out var vt)) + { + var serializer = vt.serializer; + return serializer(value!); + } + return new JValue(value); + } + + internal static T ConvertBack(JValue jValue) + { + if (KnownTypeConverters.TryGetValue(typeof(T), out var vt)) + { + var deserializer = vt.deserializer; + return (T) deserializer(jValue); + } + return jValue.ToObject()!; + } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/MemberInvokingType.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/MemberInvokingType.cs new file mode 100644 index 00000000..b71cf16f --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/MemberInvokingType.cs @@ -0,0 +1,37 @@ +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models +{ + /// + /// 自动生成的 IPC 调用代码中,目前支持的所有成员调用类型。 + /// + /// + /// 后续支持更多调用类型时,将扩充此枚举。 + /// + internal enum MemberInvokingType + { + /// + /// 未知的调用类型。 + /// 此值作为 IPC 传输过程中因为未传输或传输出错后的默认值。 + /// + Unknown, + + /// + /// 获取属性的值。 + /// + GetProperty, + + /// + /// 设置属性的值。 + /// + SetProperty, + + /// + /// 方法调用。 + /// + Method, + + /// + /// 异步方法调用。 + /// + AsyncMethod, + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/ObjectToIpcProxyJointConverter.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/ObjectToIpcProxyJointConverter.cs new file mode 100644 index 00000000..179a3d64 --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/Models/ObjectToIpcProxyJointConverter.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +using dotnetCampus.Ipc.CompilerServices.Attributes; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models +{ + /// + /// 在跨进程的 IPC 方法调用中,方法参数或返回值可能并不是基本类型,而是另一个 IPC 类型的实例。 + /// 本类型提供辅助方法判断和转换 到可被序列化的 IPC 类型,并辅助生成 IPC 类型的代理和对接。 + /// + internal static class ObjectToIpcProxyJointConverter + { + /// + /// 当从 IPC 传输过来一些对象信息时,通过此方法可以判断此对象是否是一个 IPC 公开的对象。 + /// 如果是一个 IPC 公开的对象,则可以从 获取到这个对象的本地代理。 + /// + /// 基于 .NET 类型进行 IPC 传输的上下文信息。 + /// IPC 对方端。 + /// 对象真实实例的类型名称。 + /// 如果可能有同一个契约类型的多个对象,则在此传入此对象的 IPC 访问 Id。 + /// 如果经判断是一个 IPC 公开的对象,则可以从此参数中获取到这个对象的本地代理。 + /// 如果这是一个 IPC 公开的对象,则返回 true,如果只是一个普通对象,则返回 false。 + public static bool TryCreateProxyFromSerializationInfo(this GeneratedProxyJointIpcContext context, + IPeerProxy peerProxy, string? assemblyQualifiedName, string? objectId, + [NotNullWhen(true)] out object? proxy) + { + if (assemblyQualifiedName is { } typeName + && Type.GetType(assemblyQualifiedName) is { } instanceType + && instanceType.IsDefined(typeof(IpcPublicAttribute)) + && instanceType.GetCustomAttribute() is { } attribute) + { + var proxyType = attribute.ProxyType; + var proxyInstance = (GeneratedIpcProxy) Activator.CreateInstance(proxyType)!; + proxyInstance.Context = context; + proxyInstance.PeerProxy = peerProxy; + proxyInstance.ObjectId = objectId; + proxy = proxyInstance; + return true; + } + proxy = default; + return false; + } + + public static bool TryCreateSerializationInfoFromIpcRealInstance(this GeneratedProxyJointIpcContext context, + object? realInstance, + [NotNullWhen(true)] out string? objectId, + [NotNullWhen(true)] out string? assemblyQualifiedName) + { + if (realInstance?.GetType() is { } type + && type.IsDefined(typeof(IpcPublicAttribute)) + && type.GetCustomAttribute() is { } attribute) + { + objectId = Guid.NewGuid().ToString(); + var jointInstance = (GeneratedIpcJoint) Activator.CreateInstance(attribute.JointType)!; + jointInstance.SetInstance(realInstance); + context.JointManager.AddPublicIpcObject(attribute.ContractType, jointInstance, objectId); + assemblyQualifiedName = type.AssemblyQualifiedName!; + return true; + } + objectId = null; + assemblyQualifiedName = null; + return false; + } + } +} diff --git a/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/PublicIpcJointManager.cs b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/PublicIpcJointManager.cs new file mode 100644 index 00000000..b91139cf --- /dev/null +++ b/src/dotnetCampus.Ipc/CompilerServices/GeneratedProxies/PublicIpcJointManager.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Contexts; +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies.Models; +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils; + +namespace dotnetCampus.Ipc.CompilerServices.GeneratedProxies +{ + /// + /// 辅助管理 IPC 远程代理的自动生成的对接类。 + /// + public class PublicIpcJointManager + { + /// + /// 包含所有目前已公开的 IPC 实例。 + /// + private readonly ConcurrentDictionary<(string typeFullName, string objectId), GeneratedIpcJoint> _joints = new(); + private readonly GeneratedProxyJointIpcContext _context; + private readonly IpcContext _ipcContext; + + internal PublicIpcJointManager(GeneratedProxyJointIpcContext context, IpcContext ipcContext) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _ipcContext = ipcContext ?? throw new ArgumentNullException(nameof(ipcContext)); + } + + /// + /// 以契约 的方式公开对象 ,使其可被其他进程发现并使用。 + /// + /// 契约类型。 + /// 如果希望同一个契约类型公开多个实例,需用此 Id 加以区分。 + /// 此契约类型的对接类。 + public void AddPublicIpcObject(GeneratedIpcJoint joint, string? ipcObjectId = "") where TContract : class + { + if (!_joints.TryAdd((typeof(TContract).FullName!, ipcObjectId ?? ""), joint)) + { + throw new InvalidOperationException($"不可重复公开相同契约 {typeof(TContract).FullName} 且相同 Id {ipcObjectId} 的多个 IPC 对接实例。"); + } + } + + /// + /// 以契约 的方式设置对象 可被其他进程发现并使用。 + /// + /// 契约类型。 + /// 如果希望同一个契约类型公开多个实例,需用此 Id 加以区分。 + /// 此契约类型的对接类。 + public void AddPublicIpcObject(Type contractType, GeneratedIpcJoint joint, string? ipcObjectId = "") + { + if (!_joints.TryAdd((contractType.FullName!, ipcObjectId ?? ""), joint)) + { + throw new InvalidOperationException($"不可重复公开相同契约 {contractType.FullName} 且相同 Id {ipcObjectId} 的多个 IPC 对接实例。"); + } + } + + /// + /// 当收到 IPC 消息时调用此方法可以尝试检查此消息是否是来自一个 IPC 类型的代理。如果是,那么会查询本进程中的对接类来对接此代理。 + /// + /// IPC 消息的消息体。 + /// IPC 回应消息的消息体,可异步等待。 + /// + /// 当此消息来自 IPC 代理,并且本进程存在能对接此代理的类型,则返回 true;否则返回 false。 + /// + public bool TryJoint(IIpcRequestContext request, out Task responseTask) + { + if (GeneratedProxyMemberInvokeModel.TryDeserialize(request.IpcBufferMessage, out var requestModel) + && TryFindJoint(requestModel, out var joint) + && requestModel.MemberName is { } memberName) + { + var returnModelTask = InvokeAndReturn(joint, requestModel, request.Peer); + responseTask = GeneratedIpcJointResponse.FromAsyncReturnModel(returnModelTask) + .As(); + return true; + } + + responseTask = Task.FromResult(GeneratedIpcJointResponse.Empty); + return false; + } + + internal IEnumerable EnumerateJointNames() + { + foreach (var joint in _joints) + { + var (typeName, objectId) = joint.Key; + if (string.IsNullOrEmpty(objectId)) + { + yield return typeName; + } + else + { + yield return $"{typeName}({objectId})"; + } + } + } + + private bool TryFindJoint(GeneratedProxyMemberInvokeModel model, [NotNullWhen(true)] out GeneratedIpcJoint? joint) + { + var id = model.Id ?? ""; + if (string.IsNullOrWhiteSpace(model.ContractFullTypeName) + || string.IsNullOrWhiteSpace(model.MemberName) + || model.CallType is MemberInvokingType.Unknown) + { + joint = null; + return false; + } + + if (_joints.TryGetValue((model.ContractFullTypeName, id), out joint)) + { + return true; + } + else + { +#if DEBUG + throw new InvalidOperationException($"没有发现针对 {model.ContractFullTypeName} 的对接,无法处理此消息。消息处理丢失可能导致对方端等死,必须调查。"); +#else + joint = null; + return false; +#endif + } + } + + private async Task InvokeAndReturn( + GeneratedIpcJoint joint, + GeneratedProxyMemberInvokeModel requestModel, + IPeerProxy peer) + { + GeneratedProxyMemberReturnModel? @return; + try + { + var args = ExtractArgsFromArgsModel(requestModel.Args, peer); + var returnValue = await InvokeMember(joint, requestModel.CallType, requestModel.MemberName!, args).ConfigureAwait(false); + @return = CreateReturnModelFromReturnObject(returnValue); +#if DEBUG + @return.Invoking = requestModel; +#endif + } + catch (Exception ex) + { + @return = new GeneratedProxyMemberReturnModel(ex); + } + return @return; + } + + /// + /// 预处理一组需要进行 IPC 代理访问的参数。 + /// + /// 参数模型列表。 + /// 如果某个参数的模型表示需要通过代理访问一个 IPC 远端对象,则会用到这个远端。 + /// 参数实例列表。 + /// + private object?[]? ExtractArgsFromArgsModel(GeneratedProxyObjectModel?[]? argModels, IPeerProxy peer) + { + if (argModels is null) + { + return null; + } + + if (argModels.Length is 0) + { + return new object?[0]; + } + + var args = new object?[argModels.Length]; + for (var i = 0; i < args.Length; i++) + { + var argModel = argModels[i]; + if (argModel is not null && _context.TryCreateProxyFromSerializationInfo(peer, argModel.AssemblyQualifiedName, argModel.Id, out var proxy)) + { + args[i] = proxy; + } + else + { + args[i] = argModel?.Value; + } + } + return args; + } + + /// + /// 处理一个 IPC 对接的返回值。 + /// 额外的,如果其返回的是一个 IPC 公开的类型,则将其加入到新的 IPC 对接管理中。 + /// + /// 真实的返回值或返回实例。 + /// 可被序列化进行 IPC 传输的返回值模型。 + private GeneratedProxyMemberReturnModel CreateReturnModelFromReturnObject(object? returnValue) + { + if (_context.TryCreateSerializationInfoFromIpcRealInstance(returnValue, out var objectId, out var assemblyQualifiedName)) + { + return new GeneratedProxyMemberReturnModel + { + Return = new GeneratedProxyObjectModel + { + Id = objectId, + AssemblyQualifiedName = assemblyQualifiedName, + } + }; + } + else + { + return new GeneratedProxyMemberReturnModel(returnValue); + } + } + + private static async Task InvokeMember(GeneratedIpcJoint joint, MemberInvokingType callType, string memberName, object?[]? args) + { + return callType switch + { + MemberInvokingType.GetProperty => joint.GetProperty(memberName), + MemberInvokingType.SetProperty => joint.SetProperty(memberName, args?.FirstOrDefault()), + MemberInvokingType.Method => joint.CallMethod(memberName, args), + MemberInvokingType.AsyncMethod => await joint.CallMethodAsync(memberName, args).ConfigureAwait(false), + _ => null, + }; + } + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/AckArgs.cs b/src/dotnetCampus.Ipc/Context/AckArgs.cs similarity index 90% rename from src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/AckArgs.cs rename to src/dotnetCampus.Ipc/Context/AckArgs.cs index 8f61bbf3..3f32250a 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/AckArgs.cs +++ b/src/dotnetCampus.Ipc/Context/AckArgs.cs @@ -1,6 +1,8 @@ using System; -namespace dotnetCampus.Ipc.PipeCore.Context +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context { /// /// 回复消息的事件参数 diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/AckTask.cs b/src/dotnetCampus.Ipc/Context/AckTask.cs similarity index 76% rename from src/dotnetCampus.Ipc.PipeCore/Context/AckTask.cs rename to src/dotnetCampus.Ipc/Context/AckTask.cs index 08378bb7..818296e5 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/AckTask.cs +++ b/src/dotnetCampus.Ipc/Context/AckTask.cs @@ -1,6 +1,9 @@ using System.Threading.Tasks; -namespace dotnetCampus.Ipc.PipeCore.Context +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context { /// /// 回复 Ack 的任务,用于在 收到回复的时候设置任务完成 @@ -13,19 +16,21 @@ class AckTask /// /// /// - /// 用于调试的信息 - public AckTask(string peerName, in Ack ack, TaskCompletionSource task, string summary) + /// 用于调试的信息 + public AckTask(string peerName, in Ack ack, TaskCompletionSource task, string tag) { PeerName = peerName; Ack = ack; Task = task; - Summary = summary; + Summary = tag; } public TaskCompletionSource Task { get; } + public string Summary { get; } public Ack Ack { get; } + public string PeerName { get; } } } diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/DelegateIpcRequestHandler.cs b/src/dotnetCampus.Ipc/Context/DelegateIpcRequestHandler.cs similarity index 66% rename from src/dotnetCampus.Ipc.PipeCore/Context/DelegateIpcRequestHandler.cs rename to src/dotnetCampus.Ipc/Context/DelegateIpcRequestHandler.cs index e17167e4..1fba3b2d 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/DelegateIpcRequestHandler.cs +++ b/src/dotnetCampus.Ipc/Context/DelegateIpcRequestHandler.cs @@ -1,8 +1,9 @@ using System; using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -namespace dotnetCampus.Ipc.PipeCore.Context +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context { /// /// 基于委托的 IPC 请求处理 @@ -13,7 +14,7 @@ public class DelegateIpcRequestHandler : IIpcRequestHandler /// 创建基于委托的 IPC 请求处理 /// /// - public DelegateIpcRequestHandler(Func> handler) + public DelegateIpcRequestHandler(Func> handler) { _handler = handler; } @@ -21,16 +22,16 @@ public DelegateIpcRequestHandler(Func /// 创建基于委托的 IPC 请求处理 /// - public DelegateIpcRequestHandler(Func handler) + public DelegateIpcRequestHandler(Func handler) { _handler = c => Task.FromResult(handler(c)); } - Task IIpcRequestHandler.HandleRequestMessage(IIpcRequestContext requestContext) + Task IIpcRequestHandler.HandleRequest(IIpcRequestContext requestContext) { return _handler(requestContext); } - private readonly Func> _handler; + private readonly Func> _handler; } } diff --git a/src/dotnetCampus.Ipc.Abstractions/IIpcRequestContext.cs b/src/dotnetCampus.Ipc/Context/IIpcRequestContext.cs similarity index 60% rename from src/dotnetCampus.Ipc.Abstractions/IIpcRequestContext.cs rename to src/dotnetCampus.Ipc/Context/IIpcRequestContext.cs index 6ab0a774..aafabe3d 100644 --- a/src/dotnetCampus.Ipc.Abstractions/IIpcRequestContext.cs +++ b/src/dotnetCampus.Ipc/Context/IIpcRequestContext.cs @@ -1,6 +1,6 @@ -using dotnetCampus.Ipc.Abstractions.Context; +using dotnetCampus.Ipc.Messages; -namespace dotnetCampus.Ipc.Abstractions +namespace dotnetCampus.Ipc.Context { /// /// 客户端请求的上下文 @@ -10,16 +10,21 @@ public interface IIpcRequestContext /// /// 是否已处理 /// - bool Handle { get; set; } + bool Handled { get; set; } /// /// 收到客户端发生过来的消息 /// - IpcBufferMessage IpcBufferMessage { get; } + IpcMessage IpcBufferMessage { get; } /// /// 发送请求的对方 /// IPeerProxy Peer { get; } } + + internal interface ICoreIpcRequestContext + { + public CoreMessageType CoreMessageType { get; } + } } diff --git a/src/dotnetCampus.Ipc/Context/IPeerConnectionBrokenArgs.cs b/src/dotnetCampus.Ipc/Context/IPeerConnectionBrokenArgs.cs new file mode 100644 index 00000000..054ce26d --- /dev/null +++ b/src/dotnetCampus.Ipc/Context/IPeerConnectionBrokenArgs.cs @@ -0,0 +1,25 @@ +namespace dotnetCampus.Ipc.Context +{ + /// + /// 对方连接断开事件参数 + /// + public interface IPeerConnectionBrokenArgs + { + } + + /// + /// 断开的原因 + /// + public enum BrokenReason + { + /// + /// 未知原因,此时将会触发重连机制 + /// + Unknown, + + /// + /// 业务端显式退出,正常退出,不会触发重连机制 + /// + BusinessExit, + } +} diff --git a/src/dotnetCampus.Ipc.Abstractions/IPeerMessageArgs.cs b/src/dotnetCampus.Ipc/Context/IPeerMessageArgs.cs similarity index 71% rename from src/dotnetCampus.Ipc.Abstractions/IPeerMessageArgs.cs rename to src/dotnetCampus.Ipc/Context/IPeerMessageArgs.cs index fb19b51f..f765f0a3 100644 --- a/src/dotnetCampus.Ipc.Abstractions/IPeerMessageArgs.cs +++ b/src/dotnetCampus.Ipc/Context/IPeerMessageArgs.cs @@ -1,6 +1,8 @@ using System.IO; -namespace dotnetCampus.Ipc.Abstractions +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context { /// /// 收到的对方的信息事件参数 @@ -8,9 +10,9 @@ namespace dotnetCampus.Ipc.Abstractions public interface IPeerMessageArgs { /// - /// 用于读取消息的内容 + /// 来自其他端的消息。 /// - public Stream Message { get; } + public IpcMessage Message { get; } /// /// 对方的名字,此名字是对方的服务器名字,可以用来连接 diff --git a/src/dotnetCampus.Ipc/Context/IPeerReconnectedArgs.cs b/src/dotnetCampus.Ipc/Context/IPeerReconnectedArgs.cs new file mode 100644 index 00000000..7e8344c3 --- /dev/null +++ b/src/dotnetCampus.Ipc/Context/IPeerReconnectedArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace dotnetCampus.Ipc.Context +{ + /// + /// 对方断开重连事件参数 + /// + public interface IPeerReconnectedArgs + { + + } + + class PeerReconnectedArgs : EventArgs, IPeerReconnectedArgs + { + + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/IpcBufferMessageContext.cs b/src/dotnetCampus.Ipc/Context/IpcBufferMessageContext.cs similarity index 81% rename from src/dotnetCampus.Ipc.PipeCore/Context/IpcBufferMessageContext.cs rename to src/dotnetCampus.Ipc/Context/IpcBufferMessageContext.cs index 85028b6f..491d4b7f 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/IpcBufferMessageContext.cs +++ b/src/dotnetCampus.Ipc/Context/IpcBufferMessageContext.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; -using dotnetCampus.Ipc.Abstractions.Context; -namespace dotnetCampus.Ipc.PipeCore.Context +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context { /// /// 包含多条信息的上下文 @@ -16,9 +17,9 @@ readonly struct IpcBufferMessageContext /// 表示写入的是什么内容,用于调试 /// 命令类型,用于分开框架内的消息和业务的 /// - public IpcBufferMessageContext(string summary, IpcMessageCommandType ipcMessageCommandType, params IpcBufferMessage[] ipcBufferMessageList) + public IpcBufferMessageContext(string summary, IpcMessageCommandType ipcMessageCommandType, params IpcMessageBody[] ipcBufferMessageList) { - Summary = summary; + Tag = summary; IpcMessageCommandType = ipcMessageCommandType; IpcBufferMessageList = ipcBufferMessageList; } @@ -28,12 +29,12 @@ public IpcBufferMessageContext(string summary, IpcMessageCommandType ipcMessageC /// public IpcMessageCommandType IpcMessageCommandType { get; } - public IpcBufferMessage[] IpcBufferMessageList { get; } + public IpcMessageBody[] IpcBufferMessageList { get; } /// /// 表示内容是什么用于调试 /// - public string Summary { get; } + public string Tag { get; } public int Length { @@ -42,7 +43,7 @@ public int Length var length = 0; foreach (var ipcBufferMessage in IpcBufferMessageList) { - length += ipcBufferMessage.Count; + length += ipcBufferMessage.Length; } return length; @@ -56,8 +57,8 @@ public int Length /// 将加入的内容合并到新的消息前面,为 true 合并到前面,否则合并到后面 /// /// - public IpcBufferMessageContext BuildWithCombine(IpcMessageCommandType ipcMessageCommandType, bool mergeBefore, params IpcBufferMessage[] ipcBufferMessageList) - => BuildWithCombine(Summary, ipcMessageCommandType, mergeBefore, ipcBufferMessageList); + public IpcBufferMessageContext BuildWithCombine(IpcMessageCommandType ipcMessageCommandType, bool mergeBefore, params IpcMessageBody[] ipcBufferMessageList) + => BuildWithCombine(Tag, ipcMessageCommandType, mergeBefore, ipcBufferMessageList); /// /// 和其他的合并然后创建新的 @@ -67,9 +68,9 @@ public IpcBufferMessageContext BuildWithCombine(IpcMessageCommandType ipcMessage /// 将加入的内容合并到新的消息前面,为 true 合并到前面,否则合并到后面 /// /// - public IpcBufferMessageContext BuildWithCombine(string summary, IpcMessageCommandType ipcMessageCommandType, bool mergeBefore, params IpcBufferMessage[] ipcBufferMessageList) + public IpcBufferMessageContext BuildWithCombine(string summary, IpcMessageCommandType ipcMessageCommandType, bool mergeBefore, params IpcMessageBody[] ipcBufferMessageList) { - var newIpcBufferMessageList = new List(ipcBufferMessageList.Length + IpcBufferMessageList.Length); + var newIpcBufferMessageList = new List(ipcBufferMessageList.Length + IpcBufferMessageList.Length); if (mergeBefore) { newIpcBufferMessageList.AddRange(ipcBufferMessageList); diff --git a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestArgs.cs b/src/dotnetCampus.Ipc/Context/IpcClientRequestArgs.cs similarity index 64% rename from src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestArgs.cs rename to src/dotnetCampus.Ipc/Context/IpcClientRequestArgs.cs index cd2e1e76..ee0282d9 100644 --- a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestArgs.cs +++ b/src/dotnetCampus.Ipc/Context/IpcClientRequestArgs.cs @@ -1,8 +1,8 @@ using System; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore.Context; -namespace dotnetCampus.Ipc.PipeCore.IpcPipe +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context { /// /// 来自客户端的请求事件参数 @@ -14,10 +14,11 @@ public class IpcClientRequestArgs : EventArgs /// /// /// - internal IpcClientRequestArgs(in IpcClientRequestMessageId messageId, in IpcBufferMessage ipcBufferMessage) + internal IpcClientRequestArgs(in IpcClientRequestMessageId messageId, in IpcMessageBody ipcBufferMessage, IpcMessageCommandType messageCommandType) { MessageId = messageId; - IpcBufferMessage = ipcBufferMessage; + IpcMessageBody = ipcBufferMessage; + MessageCommandType = messageCommandType; } /// @@ -28,6 +29,8 @@ internal IpcClientRequestArgs(in IpcClientRequestMessageId messageId, in IpcBuff /// /// 收到客户端发生过来的消息 /// - public IpcBufferMessage IpcBufferMessage { get; } + public IpcMessageBody IpcMessageBody { get; } + + internal IpcMessageCommandType MessageCommandType { get; } } } diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/IpcConfiguration.cs b/src/dotnetCampus.Ipc/Context/IpcConfiguration.cs similarity index 51% rename from src/dotnetCampus.Ipc.PipeCore/Context/IpcConfiguration.cs rename to src/dotnetCampus.Ipc/Context/IpcConfiguration.cs index 30be0e37..b2df6471 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/IpcConfiguration.cs +++ b/src/dotnetCampus.Ipc/Context/IpcConfiguration.cs @@ -1,13 +1,24 @@ -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.PipeCore.Utils; +using System; +using System.Collections.Generic; -namespace dotnetCampus.Ipc.PipeCore.Context +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Utils.Buffers; +using dotnetCampus.Ipc.Utils.Logging; + +namespace dotnetCampus.Ipc.Context { /// /// 进程间通讯的配置 /// public class IpcConfiguration { + /// + /// 自动重连 Peer 是否开启,如开启,在断开后将会自动尝试去重新连接 + /// + public bool AutoReconnectPeers { get; set; } = false; + + private readonly List _ipcRequestHandlers = new(); + /// /// 消息内容允许最大的长度。超过这个长度,咋不上天 /// @@ -21,6 +32,11 @@ public class IpcConfiguration /// public ISharedArrayPool SharedArrayPool { get; set; } = new SharedArrayPool(); + /// + /// 为 IPC 记录日志。 + /// + public Func? IpcLoggerProvider { get; set; } + /// /// 处理通讯相关业务的定义 /// @@ -41,5 +57,30 @@ public class IpcConfiguration */ public byte[] MessageHeader { set; get; } = {0x64, 0x6F, 0x74, 0x6E, 0x65, 0x74, 0x20, 0x63, 0x61, 0x6D, 0x70, 0x75, 0x73}; + + /// + /// 提供给框架调用,用于注入框架特殊处理的请求处理器。 + /// + /// 框架特殊处理的请求处理器。 + internal void AddFrameworkRequestHandlers(params IIpcRequestHandler[] handlers) + { + _ipcRequestHandlers.AddRange(handlers); + } + + /// + /// 获取框架和业务的请求处理器。 + /// + /// 按顺序返回框架注入的请求处理器、业务默认指定的请求处理器。 + internal IEnumerable GetIpcRequestHandlers() + { + foreach (var handler in _ipcRequestHandlers) + { + yield return handler; + } + if (DefaultIpcRequestHandler is { } @default) + { + yield return @default; + } + } } } diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/IpcContext.cs b/src/dotnetCampus.Ipc/Context/IpcContext.cs similarity index 56% rename from src/dotnetCampus.Ipc.PipeCore/Context/IpcContext.cs rename to src/dotnetCampus.Ipc/Context/IpcContext.cs index 626ebc89..fa8c74b0 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/IpcContext.cs +++ b/src/dotnetCampus.Ipc/Context/IpcContext.cs @@ -1,6 +1,10 @@ -using dotnetCampus.Ipc.PipeCore.IpcPipe; +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Pipes; +using dotnetCampus.Ipc.Threading.Tasks; +using dotnetCampus.Ipc.Utils.Logging; -namespace dotnetCampus.Ipc.PipeCore.Context +namespace dotnetCampus.Ipc.Context { /// /// 用于作为 Ipc 库的上下文,包括各个过程需要使用的工具和配置等 @@ -12,6 +16,8 @@ public class IpcContext /// public const string DefaultPipeName = "dotnet campus"; + private static readonly IpcTask DefaultIpcTask = new(); + /// /// 创建上下文 /// @@ -27,12 +33,15 @@ public IpcContext(IpcProvider ipcProvider, string pipeName, IpcConfiguration? ip IpcRequestHandlerProvider = new IpcRequestHandlerProvider(this); IpcConfiguration = ipcConfiguration ?? new IpcConfiguration(); + GeneratedProxyJointIpcContext = new GeneratedProxyJointIpcContext(this); - Logger = new IpcLogger(this); + Logger = IpcConfiguration.IpcLoggerProvider?.Invoke(pipeName) ?? new IpcLogger(pipeName); } internal AckManager AckManager { get; } + internal GeneratedProxyJointIpcContext GeneratedProxyJointIpcContext { get; } + /// public override string ToString() { @@ -57,8 +66,22 @@ public override string ToString() internal ILogger Logger { get; } /// - /// 规定回应 ack 的值使用的 ack 是最大值 + /// 供 IPC 使用的线程池。 + /// 特点为按顺序触发执行,但如果前一个任务执行超时,下一个任务将转到其他线程中执行。 + /// 适用于: + /// 1. 期望执行顺序与触发顺序一致; + /// 2. 大多数为小型任务,但可能会出现一些难以预料到的长时间的任务; + /// 3. 不阻塞调用线程。 /// - internal Ack AckUsedForReply { get; } = new Ack(ulong.MaxValue); + internal IpcTask TaskPool { get; } = DefaultIpcTask; + + // 当前干掉回应的逻辑 + ///// + ///// 规定回应 ack 的值使用的 ack 是最大值 + ///// + //internal Ack AckUsedForReply { get; } = new Ack(ulong.MaxValue); + + internal bool IsDisposing { set; get; } + internal bool IsDisposed { set; get; } } } diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/IpcInternalPeerConnectedArgs.cs b/src/dotnetCampus.Ipc/Context/IpcInternalPeerConnectedArgs.cs similarity index 92% rename from src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/IpcInternalPeerConnectedArgs.cs rename to src/dotnetCampus.Ipc/Context/IpcInternalPeerConnectedArgs.cs index eb500dc6..84885080 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/IpcInternalPeerConnectedArgs.cs +++ b/src/dotnetCampus.Ipc/Context/IpcInternalPeerConnectedArgs.cs @@ -1,7 +1,10 @@ using System; using System.IO; -namespace dotnetCampus.Ipc.PipeCore.Context +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context { /// /// 对方连接的事件参数 diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerConnectedArgs.cs b/src/dotnetCampus.Ipc/Context/PeerConnectedArgs.cs similarity index 89% rename from src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerConnectedArgs.cs rename to src/dotnetCampus.Ipc/Context/PeerConnectedArgs.cs index 3e83ac9d..9035cc28 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerConnectedArgs.cs +++ b/src/dotnetCampus.Ipc/Context/PeerConnectedArgs.cs @@ -1,6 +1,8 @@ using System; -namespace dotnetCampus.Ipc.PipeCore.Context +using dotnetCampus.Ipc.Pipes; + +namespace dotnetCampus.Ipc.Context { /// /// 对方连接的事件参数 diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerConnectionBrokenArgs.cs b/src/dotnetCampus.Ipc/Context/PeerConnectionBrokenArgs.cs similarity index 68% rename from src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerConnectionBrokenArgs.cs rename to src/dotnetCampus.Ipc/Context/PeerConnectionBrokenArgs.cs index 9b6a581a..ce922eac 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerConnectionBrokenArgs.cs +++ b/src/dotnetCampus.Ipc/Context/PeerConnectionBrokenArgs.cs @@ -1,7 +1,6 @@ using System; -using dotnetCampus.Ipc.Abstractions.Context; -namespace dotnetCampus.Ipc.PipeCore.Context +namespace dotnetCampus.Ipc.Context { /// /// 对方连接断开事件参数 diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerMessageArgs.cs b/src/dotnetCampus.Ipc/Context/PeerMessageArgs.cs similarity index 89% rename from src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerMessageArgs.cs rename to src/dotnetCampus.Ipc/Context/PeerMessageArgs.cs index ae180579..8da2ac1a 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/EventArgs_/PeerMessageArgs.cs +++ b/src/dotnetCampus.Ipc/Context/PeerMessageArgs.cs @@ -1,9 +1,10 @@ using System; using System.Diagnostics; using System.IO; -using dotnetCampus.Ipc.Abstractions; -namespace dotnetCampus.Ipc.PipeCore.Context +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context { /// /// 对方通讯的消息事件参数 @@ -18,7 +19,7 @@ public class PeerMessageArgs : EventArgs, IPeerMessageArgs /// /// [DebuggerStepThrough] - public PeerMessageArgs(string peerName, Stream message, in Ack ack, IpcMessageCommandType messageCommandType) + internal PeerMessageArgs(string peerName, IpcMessage message, in Ack ack, IpcMessageCommandType messageCommandType) { Message = message; Ack = ack; @@ -29,7 +30,7 @@ public PeerMessageArgs(string peerName, Stream message, in Ack ack, IpcMessageCo /// /// 用于读取消息的内容 /// - public Stream Message { get; } + public IpcMessage Message { get; } /// /// 消息编号 diff --git a/src/dotnetCampus.Ipc/Context/PeerStreamMessageArgs.cs b/src/dotnetCampus.Ipc/Context/PeerStreamMessageArgs.cs new file mode 100644 index 00000000..8d353fb6 --- /dev/null +++ b/src/dotnetCampus.Ipc/Context/PeerStreamMessageArgs.cs @@ -0,0 +1,78 @@ +using System; +using System.Diagnostics; +using System.IO; + +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Context +{ + /// + /// 替代 在 IPC 框架内进行高性能传递。 + /// + internal class PeerStreamMessageArgs : EventArgs + { + /// + /// 创建对方通讯的消息事件参数 + /// + /// + /// + /// + /// + /// + [DebuggerStepThrough] + internal PeerStreamMessageArgs(IpcMessageContext ipcMessageContext, string peerName, Stream messageStream, in Ack ack, IpcMessageCommandType messageCommandType) + { + IpcMessageContext = ipcMessageContext; + PeerName = peerName; + MessageStream = messageStream; + Ack = ack; + MessageCommandType = messageCommandType; + } + + internal IpcMessageContext IpcMessageContext { get; } + + /// + /// 用于读取消息的内容 + /// + internal Stream MessageStream { get; } + + /// + /// 消息编号 + /// + public Ack Ack { get; } + + /// + /// 对方的名字,此名字是对方的服务器名字,可以用来连接 + /// + public string PeerName { get; } + + internal IpcMessageCommandType MessageCommandType { get; } + + /// + /// 表示是否被上一级处理了,可以通过 了解处理者的信息 + /// + public bool Handle { private set; get; } + + /// + /// 处理者的消息 + /// + /// 框架大了,不能只有 一个属性,还需要能做到调试,调试是谁处理了,因此加添加了这个属性 + public string? HandlerMessage { private set; get; } + + /// + /// 设置被处理,同时添加 用于调试的信息 + /// + /// 用于调试的信息,请记录是谁设置的,原因是什么 + public void SetHandle(string message) + { + Handle = true; + HandlerMessage = message; + } + + internal PeerMessageArgs ToPeerMessageArgs() + { + var message = new IpcMessage("MessageReceived", new IpcMessageBody(IpcMessageContext.MessageBuffer, (int) MessageStream.Position, (int) (IpcMessageContext.MessageLength - MessageStream.Position))); + return new PeerMessageArgs(PeerName, message, Ack, MessageCommandType); + } + } +} diff --git a/src/dotnetCampus.Ipc/Diagnostics/IIpcMessageInspector.cs b/src/dotnetCampus.Ipc/Diagnostics/IIpcMessageInspector.cs new file mode 100644 index 00000000..8d23d19f --- /dev/null +++ b/src/dotnetCampus.Ipc/Diagnostics/IIpcMessageInspector.cs @@ -0,0 +1,32 @@ +namespace dotnetCampus.Ipc.Diagnostics +{ + /// + /// 实现此接口并注册到 中可检查所有 IPC 收发的消息内容,以供调试。 + /// + public interface IIpcMessageInspector + { + /// + /// 实现此方法以检查从业务端发起的消息。 + /// + /// 包含消息发送的上下文。 + void Send(IpcMessageInspectionContext context); + + /// + /// 实现此方法以检查从框架最终发出的消息。 + /// + /// 包含消息发送的上下文。 + void SendCore(IpcMessageInspectionContext context); + + /// + /// 实现此方法以检查从框架收到的最原始的消息内容。 + /// + /// 包含消息接收的上下文。 + void ReceiveCore(IpcMessageInspectionContext context); + + /// + /// 实现此方法以检查从收到消息后发到业务后的业务部分。 + /// + /// 包含消息接收的上下文。 + void Receive(IpcMessageInspectionContext context); + } +} diff --git a/src/dotnetCampus.Ipc/Diagnostics/IpcMessageInspectionContext.cs b/src/dotnetCampus.Ipc/Diagnostics/IpcMessageInspectionContext.cs new file mode 100644 index 00000000..5318a18f --- /dev/null +++ b/src/dotnetCampus.Ipc/Diagnostics/IpcMessageInspectionContext.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; + +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Diagnostics +{ + /// + /// 为 的检查提供上下文。 + /// + public sealed class IpcMessageInspectionContext + { + private readonly IEnumerable _messageParts; + + internal IpcMessageInspectionContext(string localPeerName, string remotePeerName, Ack? ack, string tag, IEnumerable messageParts) + { + LocalPeerName = localPeerName ?? throw new ArgumentNullException(nameof(localPeerName)); + RemotePeerName = remotePeerName ?? throw new ArgumentNullException(nameof(remotePeerName)); + Ack = ack; + Tag = tag ?? throw new ArgumentNullException(nameof(tag)); + _messageParts = messageParts ?? throw new ArgumentNullException(nameof(messageParts)); + } + + /// + /// 本地 IPC 服务名称。 + /// + public string LocalPeerName { get; } + + /// + /// 发送目标的名称。 + /// + public string RemotePeerName { get; } + + /// + /// 标记此消息的描述性信息。 + /// + public string Tag { get; } + + /// + /// 消息的序号。 + /// + public Ack? Ack { get; } + + /// + /// 获取单个有意义消息的不同部分。其中,业务端检查时只能获取到业务内容的那一部分;框架端检查时可获取到消息头的非关键部分。 + /// + public IEnumerable GetMessageParts() => _messageParts; + } +} diff --git a/src/dotnetCampus.Ipc/Diagnostics/IpcMessageInspectorManager.cs b/src/dotnetCampus.Ipc/Diagnostics/IpcMessageInspectorManager.cs new file mode 100644 index 00000000..b72cdda7 --- /dev/null +++ b/src/dotnetCampus.Ipc/Diagnostics/IpcMessageInspectorManager.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Concurrent; + +namespace dotnetCampus.Ipc.Diagnostics +{ + /// + /// 管理 IPC 消息收发时的检查器。 + /// + public class IpcMessageInspectorManager + { + private static readonly ConcurrentDictionary Managers = new(); + + /// + /// 根据本地 Peer 名称查找 IPC 消息收发的检查器。 + /// + /// + /// + public static IpcMessageInspectorManager FromLocalPeerName(string peerName) + { + return Managers.GetOrAdd(peerName, name => new IpcMessageInspectorManager(name)); + } + + private readonly string _localPeerName; + + private readonly ConcurrentDictionary _inspectors = new(); + + private IpcMessageInspectorManager(string peerName) + { + _localPeerName = peerName ?? throw new ArgumentNullException(nameof(peerName)); + } + + /// + /// 注册一个 IPC 消息检查器。 + /// + /// + public void RegisterInspector(IIpcMessageInspector inspector) + { + _inspectors.TryAdd(inspector, inspector); + } + + internal void Call(Action caller) + { + foreach (var inspectorPair in _inspectors) + { + caller(inspectorPair.Key); + } + } + } +} diff --git a/src/dotnetCampus.Ipc/Diagnostics/IpcMessageTracker.cs b/src/dotnetCampus.Ipc/Diagnostics/IpcMessageTracker.cs new file mode 100644 index 00000000..e83c1f94 --- /dev/null +++ b/src/dotnetCampus.Ipc/Diagnostics/IpcMessageTracker.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Extensions; +using dotnetCampus.Ipc.Utils.Logging; + +namespace dotnetCampus.Ipc.Diagnostics +{ + interface IIpcMessageTracker + { + string Tag { get; } + + void Debug(string message); + } + + /// + /// 在 IPC 框架内部,提供消息收发的全程追踪。无论此消息被封装还是解包,都会在此类型的帮助下包含全程追踪信息。 + /// 在以下情况下,此追踪可完全保证某个消息的来源和去路: + /// + /// 业务方准备发一条消息直至此消息最终通过管道发出的全链路。 + /// 管道收到一条消息直至这条消息传递给业务的全链路。 + /// + /// + /// + internal class IpcMessageTracker : IIpcMessageTracker + { + /// + /// 本地 Peer 名称。 + /// + private readonly string _localPeerName; + + /// + /// 本消息将发至此 Peer 或本消息从此 Peer 来。 + /// + private readonly string _remotePeerName; + + /// + /// 输出日志的方法。 + /// + private readonly ILogger _logger; + + /// + /// 记录追踪过程中产生的所有日志。 + /// + private readonly List _trackingLogs; + + /// + /// 使用追踪器追踪某个消息。 + /// + /// 本地 Peer 名称。 + /// 本消息将发至此 Peer 或本消息从此 Peer 来。 + /// 要追踪的消息。 + /// 此消息的业务标记。 + /// 日志。 + public IpcMessageTracker(string localPeerName, string remotePeerName, T message, string tag, ILogger logger) + { + _localPeerName = localPeerName ?? throw new ArgumentNullException(nameof(localPeerName)); + _remotePeerName = remotePeerName ?? throw new ArgumentNullException(nameof(remotePeerName)); + Tag = tag ?? ""; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _trackingLogs = new(); + Message = message; + } + + /// + /// 继续追踪封装或解包的消息。 + /// + /// 本地 Peer 名称。 + /// 本消息将发至此 Peer 或本消息从此 Peer 来。 + /// 要追踪的消息。 + /// 此消息的业务标记。 + /// 日志。 + /// 已记录的追踪。 + private IpcMessageTracker(string localPeerName, string remotePeerName, T message, string tag, ILogger logger, List trackingLogs) + { + _localPeerName = localPeerName ?? throw new ArgumentNullException(nameof(localPeerName)); + _remotePeerName = remotePeerName ?? throw new ArgumentNullException(nameof(remotePeerName)); + Tag = tag ?? ""; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _trackingLogs = trackingLogs; + Message = message; + } + + /// + /// 此消息的业务标记。 + /// + public string Tag { get; } + + /// + /// 追踪的对象 + /// + public T Message { get; } + + /// + /// 对追踪的消息包 进行封装或解包后,返回对此封装或解包后的新追踪器。 + /// + /// 封装或解包后的新消息类型。 + /// 封装或解包后的新消息实例。 + /// 对新消息的追踪器,具有原消息的追踪记录。 + public IpcMessageTracker TrackNext(TNext nextMessage) + { + return new IpcMessageTracker(_localPeerName, _remotePeerName, nextMessage, Tag, _logger, _trackingLogs); + } + + [Conditional("DEBUG")] + public void Debug(string message, [CallerMemberName] string memberName = "") + { + _logger.Log(LogLevel.Trace, new EventId(0, _localPeerName), this, null, + (s, e) => $"[IPC] [{Tag}] [{memberName ?? "null"}] {message}"); + } + + void IIpcMessageTracker.Debug(string message) + { + _logger.Log(LogLevel.Trace, new EventId(0, _localPeerName), this, null, + (s, e) => $"[IPC] [{Tag}] {message}"); + } + + /// + /// 标记正在执行关键步骤,然后将全部消息内容记录下来用于调试。 + /// + /// 步骤名。 + /// 消息序号(为 null 表示无法确定 ACK)。 + /// 消息体。 + [Conditional("DEBUG")] + internal void CriticalStep(string stepName, Ack? ack, IpcMessageBody message) + { + CriticalStep(stepName, ack, new[] { message }); + } + + /// + /// 标记正在执行关键步骤,然后将全部消息内容记录下来用于调试。 + /// + /// 步骤名。 + /// 消息序号(为 null 表示无法确定 ACK)。 + /// 消息体(不含关键消息头,含其他消息头)。 + [Conditional("DEBUG")] + internal void CriticalStep(string stepName, Ack? ack, IEnumerable messages) + { + var manager = IpcMessageInspectorManager.FromLocalPeerName(_localPeerName); + var context = new IpcMessageInspectionContext(_localPeerName, _remotePeerName, ack, Tag, messages); + manager.Call(stepName switch + { + // 从业务端发起的请求或回复。 + "Send" => i => i.Send(context), + // 框架层最终发送的请求或回复。 + "SendCore" => i => i.SendCore(context), + // 框架层最开始收到的消息。 + "ReceiveCore" => i => i.ReceiveCore(context), + // 从收到后发至业务端的消息。 + "Receive" => i => i.Receive(context), + _ => throw new NotSupportedException($"暂不支持检查 {stepName} 关键步骤名称的消息。"), + }); + } + } +} diff --git a/src/dotnetCampus.Ipc/Exceptions/IpcException.cs b/src/dotnetCampus.Ipc/Exceptions/IpcException.cs new file mode 100644 index 00000000..bb15a8a2 --- /dev/null +++ b/src/dotnetCampus.Ipc/Exceptions/IpcException.cs @@ -0,0 +1,34 @@ +using System; + +namespace dotnetCampus.Ipc.Exceptions +{ + /// + /// 所有 IPC 相关的异常的基类。 + /// + public class IpcException : Exception + { + /// + /// 创建 的新实例。 + /// + public IpcException() : base() + { + } + + /// + /// 创建带有自定义消息的 的新实例。 + /// + /// 自定义消息。 + public IpcException(string message) : base(message) + { + } + + /// + /// 创建带有自定义消息和内部异常的 的新实例。 + /// + /// 自定义消息。 + /// 内部异常。 + public IpcException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/dotnetCampus.Ipc/Exceptions/IpcLocalException.cs b/src/dotnetCampus.Ipc/Exceptions/IpcLocalException.cs new file mode 100644 index 00000000..0601a7db --- /dev/null +++ b/src/dotnetCampus.Ipc/Exceptions/IpcLocalException.cs @@ -0,0 +1,32 @@ +namespace dotnetCampus.Ipc.Exceptions +{ + /// + /// 所有由本地问题导致的 IPC 异常。 + /// + public class IpcLocalException : IpcException + { + /// + /// 创建 的新实例。 + /// + public IpcLocalException() : base() + { + } + + /// + /// 创建带有自定义消息的 的新实例。 + /// + /// 自定义消息。 + public IpcLocalException(string message) : base(message) + { + } + + /// + /// 创建带有自定义消息和内部异常的 的新实例。 + /// + /// 自定义消息。 + /// 内部异常。 + public IpcLocalException(string message, System.Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/dotnetCampus.Ipc/Exceptions/IpcPeerConnectionBrokenException.cs b/src/dotnetCampus.Ipc/Exceptions/IpcPeerConnectionBrokenException.cs new file mode 100644 index 00000000..00371d9e --- /dev/null +++ b/src/dotnetCampus.Ipc/Exceptions/IpcPeerConnectionBrokenException.cs @@ -0,0 +1,15 @@ +namespace dotnetCampus.Ipc.Exceptions +{ + /// + /// 远端对方断开连接异常 + /// + public class IpcPeerConnectionBrokenException : IpcRemoteException + { + /// + /// 远端对方断开连接异常 + /// + public IpcPeerConnectionBrokenException() : base($"对方已断开") + { + } + } +} diff --git a/src/dotnetCampus.Ipc/Exceptions/IpcRemoteException.cs b/src/dotnetCampus.Ipc/Exceptions/IpcRemoteException.cs new file mode 100644 index 00000000..a0a3ef4d --- /dev/null +++ b/src/dotnetCampus.Ipc/Exceptions/IpcRemoteException.cs @@ -0,0 +1,51 @@ +using System; + +namespace dotnetCampus.Ipc.Exceptions +{ + /// + /// 所有由远端问题导致的 IPC 异常。 + /// + public class IpcRemoteException : IpcException + { + private readonly string? _remoteStackTrace; + + /// + /// 创建 的新实例。 + /// + public IpcRemoteException() + { + } + + /// + /// 创建带有自定义消息的 的新实例。 + /// + /// 自定义消息。 + public IpcRemoteException(string message) : base(message) + { + } + + /// + /// 创建带有自定义消息和远端堆栈的 的新实例。 + /// + /// 自定义消息。 + /// 远端堆栈。 + public IpcRemoteException(string message, string? remoteStackTrace) : base(message) + { + _remoteStackTrace = remoteStackTrace; + } + + /// + /// 创建带有自定义消息和内部异常的 的新实例。 + /// + /// 自定义消息。 + /// 内部异常。 + public IpcRemoteException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// 远端出现异常时的调用堆栈。 + /// + public override string StackTrace => _remoteStackTrace ?? base.StackTrace; + } +} diff --git a/src/dotnetCampus.Ipc/IIpcProvider.cs b/src/dotnetCampus.Ipc/IIpcProvider.cs new file mode 100644 index 00000000..cdec3800 --- /dev/null +++ b/src/dotnetCampus.Ipc/IIpcProvider.cs @@ -0,0 +1,15 @@ +using dotnetCampus.Ipc.Context; + +namespace dotnetCampus.Ipc +{ + /// + /// 对等 IPC 通信的总提供类型。 + /// + public interface IIpcProvider + { + /// + /// 上下文信息 + /// + IpcContext IpcContext { get; } + } +} diff --git a/src/dotnetCampus.Ipc.Abstractions/IIpcRequestHandler.cs b/src/dotnetCampus.Ipc/IIpcRequestHandler.cs similarity index 67% rename from src/dotnetCampus.Ipc.Abstractions/IIpcRequestHandler.cs rename to src/dotnetCampus.Ipc/IIpcRequestHandler.cs index 2ec9e9c9..8e26b33d 100644 --- a/src/dotnetCampus.Ipc.Abstractions/IIpcRequestHandler.cs +++ b/src/dotnetCampus.Ipc/IIpcRequestHandler.cs @@ -1,6 +1,9 @@ using System.Threading.Tasks; -namespace dotnetCampus.Ipc.Abstractions +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc { /// /// 用于在服务器端处理客户端请求的处理器 @@ -12,6 +15,6 @@ public interface IIpcRequestHandler /// /// /// - Task HandleRequestMessage(IIpcRequestContext requestContext); + Task HandleRequest(IIpcRequestContext requestContext); } } diff --git a/src/dotnetCampus.Ipc/IMessageWriter.cs b/src/dotnetCampus.Ipc/IMessageWriter.cs new file mode 100644 index 00000000..3ec55783 --- /dev/null +++ b/src/dotnetCampus.Ipc/IMessageWriter.cs @@ -0,0 +1,21 @@ +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace dotnetCampus.Ipc +{ + /// + /// 用于表示发送消息 + /// + public interface IRawMessageWriter + { + /// + /// 向服务端发送消息 + /// + /// + /// + /// + /// 这一次写入的是什么内容,用于调试 + /// + Task WriteMessageAsync(byte[] data, int offset, int length, [CallerMemberName] string tag = ""); + } +} diff --git a/src/dotnetCampus.Ipc.Abstractions/IPeerProxy.cs b/src/dotnetCampus.Ipc/IPeerProxy.cs similarity index 62% rename from src/dotnetCampus.Ipc.Abstractions/IPeerProxy.cs rename to src/dotnetCampus.Ipc/IPeerProxy.cs index ca73c558..76388f57 100644 --- a/src/dotnetCampus.Ipc.Abstractions/IPeerProxy.cs +++ b/src/dotnetCampus.Ipc/IPeerProxy.cs @@ -1,8 +1,10 @@ using System; using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions.Context; -namespace dotnetCampus.Ipc.Abstractions +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc { /// /// 用于表示远程的对方 @@ -15,27 +17,32 @@ public interface IPeerProxy string PeerName { get; } /// - /// 用于写入数据 - /// - IpcMessageWriter IpcMessageWriter { get; } - - /// - /// 当收到消息时触发 + /// 发送请求给对方,请求对方的响应。这是客户端-服务器端模式 /// - event EventHandler MessageReceived; + /// + /// + Task NotifyAsync(IpcMessage request); /// /// 发送请求给对方,请求对方的响应。这是客户端-服务器端模式 /// /// /// - Task GetResponseAsync(IpcRequestMessage request); + Task GetResponseAsync(IpcMessage request); - //IpcRequestMessage HandleIpcRequestMessage(IIpcRequestContext requestContext); + /// + /// 当收到消息时触发 + /// + event EventHandler MessageReceived; /// /// 对方连接断开事件 /// event EventHandler PeerConnectionBroken; + + /// + /// 对方断开重连 + /// + event EventHandler PeerReconnected; } } diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/AckManager.cs b/src/dotnetCampus.Ipc/Internals/AckManager.cs similarity index 98% rename from src/dotnetCampus.Ipc.PipeCore/Core_/AckManager.cs rename to src/dotnetCampus.Ipc/Internals/AckManager.cs index e98f4db1..489a275a 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/AckManager.cs +++ b/src/dotnetCampus.Ipc/Internals/AckManager.cs @@ -3,10 +3,11 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.Utils; -namespace dotnetCampus.Ipc.PipeCore +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Internals { internal class AckManager { diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/DebugContext.cs b/src/dotnetCampus.Ipc/Internals/DebugContext.cs similarity index 91% rename from src/dotnetCampus.Ipc.PipeCore/Core_/DebugContext.cs rename to src/dotnetCampus.Ipc/Internals/DebugContext.cs index 40d3febc..91dd107b 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/DebugContext.cs +++ b/src/dotnetCampus.Ipc/Internals/DebugContext.cs @@ -1,5 +1,4 @@ - -namespace dotnetCampus.Ipc.PipeCore +namespace dotnetCampus.Ipc.Internals { class DebugContext { diff --git a/src/dotnetCampus.Ipc/Internals/EmptyIpcRequestHandler.cs b/src/dotnetCampus.Ipc/Internals/EmptyIpcRequestHandler.cs new file mode 100644 index 00000000..7f49ad31 --- /dev/null +++ b/src/dotnetCampus.Ipc/Internals/EmptyIpcRequestHandler.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Internals +{ + class EmptyIpcRequestHandler : IIpcRequestHandler + { + public Task HandleRequest(IIpcRequestContext requestContext) + { + // 我又不知道业务,不知道怎么玩…… + var responseMessage = new IpcMessage(nameof(EmptyIpcRequestHandler), new IpcMessageBody(new byte[0])); + return Task.FromResult((IIpcResponseMessage) new IpcHandleRequestMessageResult(responseMessage)); + } + } +} diff --git a/src/dotnetCampus.Ipc/Internals/IClientMessageWriter.cs b/src/dotnetCampus.Ipc/Internals/IClientMessageWriter.cs new file mode 100644 index 00000000..619da799 --- /dev/null +++ b/src/dotnetCampus.Ipc/Internals/IClientMessageWriter.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Context; + +namespace dotnetCampus.Ipc.Internals +{ + internal interface IClientMessageWriter : IRawMessageWriter + { + Task WriteMessageAsync(in IpcBufferMessageContext ipcBufferMessageContext); + } +} diff --git a/src/dotnetCampus.Ipc/Internals/IpcHandleRequestMessageResult.cs b/src/dotnetCampus.Ipc/Internals/IpcHandleRequestMessageResult.cs new file mode 100644 index 00000000..d981b3ba --- /dev/null +++ b/src/dotnetCampus.Ipc/Internals/IpcHandleRequestMessageResult.cs @@ -0,0 +1,17 @@ +using System.Diagnostics; + +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Internals +{ + class IpcHandleRequestMessageResult : IIpcResponseMessage + { + [DebuggerStepThrough] + public IpcHandleRequestMessageResult(IpcMessage returnMessage) + { + ResponseMessage = returnMessage; + } + + public IpcMessage ResponseMessage { get; } + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcMessageConverter.cs b/src/dotnetCampus.Ipc/Internals/IpcMessageConverter.cs similarity index 53% rename from src/dotnetCampus.Ipc.PipeCore/Core_/IpcMessageConverter.cs rename to src/dotnetCampus.Ipc/Internals/IpcMessageConverter.cs index 5fd38cff..54e7a9ca 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcMessageConverter.cs +++ b/src/dotnetCampus.Ipc/Internals/IpcMessageConverter.cs @@ -2,11 +2,16 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.Utils; -using dotnetCampus.Ipc.PipeCore.Utils.Extensions; -namespace dotnetCampus.Ipc.PipeCore +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Diagnostics; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Buffers; +using dotnetCampus.Ipc.Utils.Extensions; +using dotnetCampus.Ipc.Utils.IO; +using dotnetCampus.Ipc.Utils.Logging; + +namespace dotnetCampus.Ipc.Internals { /// /// 消息的封包和解包代码,用于将传入的内容包装为 Ipc 通讯使用的二进制内容,或将 Ipc 通讯使用的二进制内容读取为业务端使用的内容 @@ -14,47 +19,47 @@ namespace dotnetCampus.Ipc.PipeCore internal static class IpcMessageConverter { public static async Task WriteAsync(Stream stream, byte[] messageHeader, Ack ack, - IpcBufferMessageContext ipcBufferMessageContext, ILogger logger) + IpcBufferMessageContext context) { - logger.Debug($"[{nameof(IpcMessageConverter)}] Start Write {ipcBufferMessageContext.Summary}"); + // 准备变量。 + var commandType = context.IpcMessageCommandType; + VerifyMessageLength(context.Length); - VerifyMessageLength(ipcBufferMessageContext.Length); + // 发送消息头。 + var binaryWriter = await WriteHeaderAsync(stream, messageHeader, ack, commandType).ConfigureAwait(false); - var binaryWriter = await WriteHeaderAsync(stream, messageHeader, ack, - ipcBufferMessageContext.IpcMessageCommandType); + // 发送消息长度。 + await binaryWriter.WriteAsync(context.Length).ConfigureAwait(false); - await binaryWriter.WriteAsync(ipcBufferMessageContext.Length); - foreach (var ipcBufferMessage in ipcBufferMessageContext.IpcBufferMessageList) + // 发送消息体。 + foreach (var ipcBufferMessage in context.IpcBufferMessageList) { - await stream.WriteAsync(ipcBufferMessage.Buffer, ipcBufferMessage.Start, ipcBufferMessage.Count); + await stream.WriteAsync(ipcBufferMessage.Buffer, ipcBufferMessage.Start, ipcBufferMessage.Length).ConfigureAwait(false); } - - logger.Debug($"[{nameof(IpcMessageConverter)}] Finished Write {ipcBufferMessageContext.Summary}"); } public static async Task WriteAsync(Stream stream, byte[] messageHeader, Ack ack, - IpcMessageCommandType ipcMessageCommandType, byte[] buffer, int offset, - int count, string? summary, ILogger logger) + IpcMessageCommandType ipcMessageCommandType, byte[] buffer, int offset, int count, string? tag = null) { - logger.Debug($"[{nameof(IpcMessageConverter)}] Start Write {summary}"); - - VerifyMessageLength(count); + VerifyMessageLength(count, tag); var binaryWriter = await WriteHeaderAsync(stream, messageHeader, ack, ipcMessageCommandType); await binaryWriter.WriteAsync(count); await stream.WriteAsync(buffer, offset, count); - - logger.Debug($"[{nameof(IpcMessageConverter)}] Finished Write {summary}"); } - private static void VerifyMessageLength(int messageLength) + private static void VerifyMessageLength(int messageLength, string? tag = null) { if (messageLength > IpcConfiguration.MaxMessageLength) { throw new ArgumentException($"Message Length too long MessageLength={messageLength} MaxMessageLength={IpcConfiguration.MaxMessageLength}. {DebugContext.OverMaxMessageLength}") { - Data = { { "Message Length", messageLength } } + Data = + { + { "Message Length", messageLength }, + { "Tag", tag ?? string.Empty } + } }; } } @@ -68,7 +73,7 @@ public static async Task WriteHeaderAsync(Stream stream, byte * UInt32 Version 当前IPC服务的版本 * UInt64 Ack 用于给对方确认收到消息使用 * UInt32 Empty 给以后版本使用的值 - * UInt16 Command Type 命令类型,业务端的值将会是 0 而框架层采用其他值 + * Int16 Command Type 命令类型,业务端的值将会是 0 而框架层采用其他值 * UInt32 Content Length 这条消息的内容长度 * byte[] Content 实际的内容 */ @@ -79,23 +84,23 @@ public static async Task WriteHeaderAsync(Stream stream, byte var asyncBinaryWriter = new AsyncBinaryWriter(stream); var messageHeaderLength = (ushort) messageHeader.Length; - await asyncBinaryWriter.WriteAsync(messageHeaderLength); + await asyncBinaryWriter.WriteAsync(messageHeaderLength).ConfigureAwait(false); - await stream.WriteAsync(messageHeader); + await stream.WriteAsync(messageHeader).ConfigureAwait(false); // UInt32 Version - await asyncBinaryWriter.WriteAsync(version); + await asyncBinaryWriter.WriteAsync(version).ConfigureAwait(false); // UInt64 Ack - await asyncBinaryWriter.WriteAsync(ack.Value); + await asyncBinaryWriter.WriteAsync(ack.Value).ConfigureAwait(false); // UInt32 Empty - await asyncBinaryWriter.WriteAsync(uint.MinValue); - // UInt16 Command Type 命令类型,业务端的值将会是 0 而框架层采用其他值 - ushort commandType = (ushort) ipcMessageCommandType; - await asyncBinaryWriter.WriteAsync(commandType); + await asyncBinaryWriter.WriteAsync(uint.MinValue).ConfigureAwait(false); + // Int16 Command Type 命令类型,业务端的值将会是 0 而框架层采用其他值 + var commandType = (ushort) ipcMessageCommandType; + await asyncBinaryWriter.WriteAsync(commandType).ConfigureAwait(false); return asyncBinaryWriter; } - public static async Task ReadAsync(Stream stream, + public static async Task> ReadAsync(Stream stream, byte[] messageHeader, ISharedArrayPool sharedArrayPool) { /* @@ -104,45 +109,73 @@ public static async Task ReadAsync(Stream stream, * UInt32 Version 当前IPC服务的版本 * UInt64 Ack 用于给对方确认收到消息使用 * UInt32 Empty 给以后版本使用的值 - * UInt16 Command Type 命令类型,业务端的值将会是 0 而框架层采用其他值 + * Int16 Command Type 命令类型,业务端的值将会是 0 而框架层采用其他值 * UInt32 Content Length 这条消息的内容长度 * byte[] Content 实际的内容 */ - if (!await GetHeader(stream, messageHeader, sharedArrayPool)) + var headerResult = await GetHeader(stream, messageHeader, sharedArrayPool); + if (headerResult.IsEndOfStream) + { + return StreamReadResult.EndOfStream; + } + + if (!headerResult.Result) { // 消息不对,忽略 - return new IpcMessageResult("Message Header no match"); + return new StreamReadResult(new IpcMessageResult("Message Header no match")); } - var binaryReader = new AsyncBinaryReader(stream); + var binaryReader = new AsyncBinaryReader(stream, sharedArrayPool); // UInt32 Version 当前IPC服务的版本 - var version = await binaryReader.ReadUInt32Async(); + var versionResult = await binaryReader.ReadUInt32Async(); + if (versionResult.IsEndOfStream) + { + return StreamReadResult.EndOfStream; + } + var version = versionResult.Result; Debug.Assert(version == 1); if (version == 0) { // 这是上个版本的,但是不兼容了 - return new IpcMessageResult("收到版本为 0 的旧版本消息,但是不兼容此版本"); + var ipcMessageResult = new IpcMessageResult("收到版本为 0 的旧版本消息,但是不兼容此版本"); + return new StreamReadResult(ipcMessageResult); } // UInt64 Ack 用于给对方确认收到消息使用 - var ack = await binaryReader.ReadReadUInt64Async(); + var ackResult = await binaryReader.ReadReadUInt64Async(); + if (ackResult.IsEndOfStream) + { + return StreamReadResult.EndOfStream; + } + var ack = ackResult.Result; // UInt32 Empty 给以后版本使用的值 var empty = await binaryReader.ReadUInt32Async(); - Debug.Assert(empty == 0); + Debug.Assert(empty.Result == 0); - // UInt16 Command Type 命令类型,业务端的值将会是大于 0 而框架层采用其他值 - var commandType = (IpcMessageCommandType) await binaryReader.ReadUInt16Async(); + // Int16 Command Type 命令类型,业务端的值将会是大于 0 而框架层采用其他值 + var commandTypeResult = await binaryReader.ReadUInt16Async(); + if (commandTypeResult.IsEndOfStream) + { + return StreamReadResult.EndOfStream; + } + var commandType = (IpcMessageCommandType) commandTypeResult.Result; // UInt32 Content Length 这条消息的内容长度 - var messageLength = await binaryReader.ReadUInt32Async(); + var messageLengthResult = await binaryReader.ReadUInt32Async(); + if (messageLengthResult.IsEndOfStream) + { + return StreamReadResult.EndOfStream; + } + var messageLength = messageLengthResult.Result; if (messageLength > IpcConfiguration.MaxMessageLength) { // 太长了 - return new IpcMessageResult( + var ipcMessageResult = new IpcMessageResult( $"Message Length too long MessageLength={messageLength} MaxMessageLength={IpcConfiguration.MaxMessageLength}. {DebugContext.OverMaxMessageLength}"); + return new StreamReadResult(ipcMessageResult); } var messageBuffer = sharedArrayPool.Rent((int) messageLength); @@ -152,7 +185,7 @@ public static async Task ReadAsync(Stream stream, Debug.Assert(readCount == messageLength); var ipcMessageContext = new IpcMessageContext(ack, messageBuffer, messageLength, sharedArrayPool); - return new IpcMessageResult(success: true, ipcMessageContext, commandType); + return new StreamReadResult(new IpcMessageResult(success: true, ipcMessageContext, commandType)); } private static async Task ReadBufferAsync(Stream stream, byte[] messageBuffer, int messageLength) @@ -168,15 +201,22 @@ private static async Task ReadBufferAsync(Stream stream, byte[] messageBuff return readCount; } - private static async Task GetHeader(Stream stream, byte[] messageHeader, ISharedArrayPool sharedArrayPool) + private static async Task> GetHeader(Stream stream, byte[] messageHeader, ISharedArrayPool sharedArrayPool) { - var binaryReader = new AsyncBinaryReader(stream); - var messageHeaderLength = await binaryReader.ReadUInt16Async(); + var binaryReader = new AsyncBinaryReader(stream, sharedArrayPool); + var messageHeaderLengthResult = await binaryReader.ReadUInt16Async(); + if (messageHeaderLengthResult.IsEndOfStream) + { + return StreamReadResult.EndOfStream; + } + + var messageHeaderLength = messageHeaderLengthResult.Result; + Debug.Assert(messageHeaderLength == messageHeader.Length); if (messageHeaderLength != messageHeader.Length) { // 消息不对,忽略 - return false; + return new StreamReadResult(false); } var messageHeaderBuffer = sharedArrayPool.Rent(messageHeader.Length); @@ -185,15 +225,15 @@ private static async Task GetHeader(Stream stream, byte[] messageHeader, I { var readCount = await ReadBufferAsync(stream, messageHeaderBuffer, messageHeader.Length); Debug.Assert(readCount == messageHeader.Length); - if (ByteListExtension.Equals(messageHeaderBuffer, messageHeader, readCount)) + if (ByteListExtensions.Equals(messageHeaderBuffer, messageHeader, readCount)) { // 读对了 - return true; + return new StreamReadResult(true); } else { // 发过来的消息是出错的 - return false; + return new StreamReadResult(false); } } finally diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcPipeServerMessageProvider.cs b/src/dotnetCampus.Ipc/Internals/IpcPipeServerMessageProvider.cs similarity index 78% rename from src/dotnetCampus.Ipc.PipeCore/Core_/IpcPipeServerMessageProvider.cs rename to src/dotnetCampus.Ipc/Internals/IpcPipeServerMessageProvider.cs index de347264..e7f1581b 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcPipeServerMessageProvider.cs +++ b/src/dotnetCampus.Ipc/Internals/IpcPipeServerMessageProvider.cs @@ -1,9 +1,12 @@ using System; +using System.IO; using System.IO.Pipes; using System.Threading.Tasks; -using dotnetCampus.Ipc.PipeCore.Context; -namespace dotnetCampus.Ipc.PipeCore +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Pipes; + +namespace dotnetCampus.Ipc.Internals { /// /// 提供一个客户端连接 @@ -21,7 +24,7 @@ public IpcPipeServerMessageProvider(IpcContext ipcContext, IpcServerService ipcS /// /// 被对方连接 /// - private string PeerName => ServerStreamMessageReader.PeerName; + private string PeerName => ServerStreamMessageReader?.PeerName ?? "NoConnected"; /// /// 自身的名字 @@ -48,13 +51,21 @@ public async Task Start() ); NamedPipeServerStream = namedPipeServerStream; + try + { #if NETCOREAPP - await namedPipeServerStream.WaitForConnectionAsync(); + await namedPipeServerStream.WaitForConnectionAsync().ConfigureAwait(false); #else await Task.Factory.FromAsync(namedPipeServerStream.BeginWaitForConnection, - namedPipeServerStream.EndWaitForConnection, null); + namedPipeServerStream.EndWaitForConnection, null).ConfigureAwait(false); #endif - + } + catch (IOException e) + { + // "管道已结束。" + // 当前服务关闭,此时异常符合预期 + return; + } //var streamMessageConverter = new StreamMessageConverter(namedPipeServerStream, // IpcConfiguration.MessageHeader, IpcConfiguration.SharedArrayPool); //streamMessageConverter.MessageReceived += OnClientConnectReceived; @@ -79,7 +90,7 @@ private void ServerStreamMessageConverter_AckRequested(object? sender, Ack e) } */ - private ServerStreamMessageReader ServerStreamMessageReader { set; get; } = null!; + private ServerStreamMessageReader? ServerStreamMessageReader { set; get; } /* private async void SendAck(Ack receivedAck) => await SendAckAsync(receivedAck); @@ -96,9 +107,17 @@ private async Task SendAckAsync(Ack receivedAck) public void Dispose() { - // 不在这一层释放 NamedPipeServerStream 类 - //NamedPipeServerStream.Dispose(); - ServerStreamMessageReader?.Dispose(); + if (ServerStreamMessageReader is null) + { + // 证明此时还没完全连接 + NamedPipeServerStream.Dispose(); + } + else + { + // 证明已连接完成,此时不需要释放 NamedPipeServerStream 类 + // 不在这一层释放 NamedPipeServerStream 类 + ServerStreamMessageReader.Dispose(); + } } } } diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/Peer_/PeerManager.cs b/src/dotnetCampus.Ipc/Internals/PeerManager.cs similarity index 60% rename from src/dotnetCampus.Ipc.PipeCore/Core_/Peer_/PeerManager.cs rename to src/dotnetCampus.Ipc/Internals/PeerManager.cs index 8ba7fc94..cd4fe948 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/Peer_/PeerManager.cs +++ b/src/dotnetCampus.Ipc/Internals/PeerManager.cs @@ -2,25 +2,28 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Threading.Tasks; +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Pipes; -namespace dotnetCampus.Ipc.PipeCore +namespace dotnetCampus.Ipc.Internals { class PeerManager : IDisposable { - public ConcurrentDictionary ConnectedServerManagerList { get; } = - new ConcurrentDictionary(); + public PeerManager(IpcProvider ipcProvider) + { + _ipcProvider = ipcProvider; + } public bool TryAdd(PeerProxy peerProxy) { peerProxy.PeerConnectionBroken += PeerProxy_PeerConnectionBroken; - + OnAdd(peerProxy); return ConnectedServerManagerList.TryAdd(peerProxy.PeerName, peerProxy); } - private void PeerProxy_PeerConnectionBroken(object? sender, dotnetCampus.Ipc.Abstractions.Context.IPeerConnectionBrokenArgs e) + public bool TryGetValue(string key, out PeerProxy peer) { - var peerProxy = (PeerProxy) sender!; - RemovePeerProxy(peerProxy); + return ConnectedServerManagerList.TryGetValue(key, out peer); } /// @@ -55,15 +58,30 @@ public void RemovePeerProxy(PeerProxy peerProxy) } } - + /// + /// 等待对方连接完成 + /// + /// + /// public async Task WaitForPeerConnectFinishedAsync(PeerProxy peerProxy) { await peerProxy.WaitForFinishedTaskCompletionSource.Task; + OnAdd(peerProxy); // 更新或注册,用于解决之前注册的实际上是断开的连接 ConnectedServerManagerList.AddOrUpdate(peerProxy.PeerName, peerProxy, (s, proxy) => proxy); } + private void OnAdd(PeerProxy peerProxy) + { + if (AutoReconnectPeers) + { + peerProxy.PeerReConnector ??= new PeerReConnector(peerProxy, _ipcProvider); + } + } + + private bool AutoReconnectPeers => _ipcProvider.IpcServerService.IpcContext.IpcConfiguration.AutoReconnectPeers; + public void Dispose() { foreach (var pair in ConnectedServerManagerList) @@ -71,8 +89,31 @@ public void Dispose() var peer = pair.Value; // 为什么 PeerProxy 不加上 IDisposable 方法 // 因为这个类在上层业务使用,被上层业务调释放就可以让框架不能使用 - peer.IpcClientService.Dispose(); + peer.DisposePeer(); } } + + + private ConcurrentDictionary ConnectedServerManagerList { get; } = + new ConcurrentDictionary(); + private readonly IpcProvider _ipcProvider; + + /* 项目“dotnetCampus.Ipc.PipeCore (net45)”的未合并的更改 + 在此之前: + private void PeerProxy_PeerConnectionBroken(object? sender, dotnetCampus.Ipc.Abstractions.Context.IPeerConnectionBrokenArgs e) + 在此之后: + private void PeerProxy_PeerConnectionBroken(object? sender, IPeerConnectionBrokenArgs e) + */ + private void PeerProxy_PeerConnectionBroken(object? sender, IPeerConnectionBrokenArgs e) + { + if (AutoReconnectPeers) + { + // 如果需要自动连接,那么就不需要删除记录 + return; + } + + var peerProxy = (PeerProxy) sender!; + RemovePeerProxy(peerProxy); + } } } diff --git a/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs b/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs new file mode 100644 index 00000000..f101e436 --- /dev/null +++ b/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs @@ -0,0 +1,34 @@ +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Pipes; + +namespace dotnetCampus.Ipc.Internals +{ + /// + /// 重新连接器 + /// + class PeerReConnector + { + public PeerReConnector(PeerProxy peerProxy, IpcProvider ipcProvider) + { + _peerProxy = peerProxy; + _ipcProvider = ipcProvider; + + peerProxy.PeerConnectionBroken += PeerProxy_PeerConnectionBroken; + } + + private void PeerProxy_PeerConnectionBroken(object? sender, IPeerConnectionBrokenArgs e) + { + Reconnect(); + } + + private readonly PeerProxy _peerProxy; + private readonly IpcProvider _ipcProvider; + + private async void Reconnect() + { + var ipcClientService = _ipcProvider.CreateIpcClientService(_peerProxy.PeerName); + await ipcClientService.Start(); + _peerProxy.Reconnect(ipcClientService); + } + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/PeerRegisterProvider.cs b/src/dotnetCampus.Ipc/Internals/PeerRegisterProvider.cs similarity index 85% rename from src/dotnetCampus.Ipc.PipeCore/Core_/PeerRegisterProvider.cs rename to src/dotnetCampus.Ipc/Internals/PeerRegisterProvider.cs index 77114646..62ab4df8 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/PeerRegisterProvider.cs +++ b/src/dotnetCampus.Ipc/Internals/PeerRegisterProvider.cs @@ -1,10 +1,11 @@ using System; using System.IO; using System.Text; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore.Context; -namespace dotnetCampus.Ipc.PipeCore +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Internals { /// /// 用于进行 Ipc 连接时的建立通讯,建立通讯的时候需要向对方发送自己的管道名,用于让对方连接 @@ -18,11 +19,11 @@ public IpcBufferMessageContext BuildPeerRegisterMessage(string peerName) * Int32 PipeNameLength * byte[] PipeName */ - var peerRegisterHeaderIpcBufferMessage = new IpcBufferMessage(PeerRegisterHeader); + var peerRegisterHeaderIpcBufferMessage = new IpcMessageBody(PeerRegisterHeader); var buffer = Encoding.UTF8.GetBytes(peerName); - var peerNameLengthBufferMessage = new IpcBufferMessage(BitConverter.GetBytes(buffer.Length)); - var peerNameIpcBufferMessage = new IpcBufferMessage(buffer); + var peerNameLengthBufferMessage = new IpcMessageBody(BitConverter.GetBytes(buffer.Length)); + var peerNameIpcBufferMessage = new IpcMessageBody(buffer); return new IpcBufferMessageContext($"PeerRegisterMessage PipeName={peerName}", IpcMessageCommandType.PeerRegister, peerRegisterHeaderIpcBufferMessage, peerNameLengthBufferMessage, diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/ServerStreamMessageReader.cs b/src/dotnetCampus.Ipc/Internals/ServerStreamMessageReader.cs similarity index 80% rename from src/dotnetCampus.Ipc.PipeCore/Core_/ServerStreamMessageReader.cs rename to src/dotnetCampus.Ipc/Internals/ServerStreamMessageReader.cs index d7a37a02..6dd4a36d 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/ServerStreamMessageReader.cs +++ b/src/dotnetCampus.Ipc/Internals/ServerStreamMessageReader.cs @@ -2,10 +2,15 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.Utils; -namespace dotnetCampus.Ipc.PipeCore +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Diagnostics; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Extensions; +using dotnetCampus.Ipc.Utils.IO; +using dotnetCampus.Ipc.Utils.Logging; + +namespace dotnetCampus.Ipc.Internals { /// /// 基础的数据读取 @@ -48,7 +53,7 @@ public ServerStreamMessageReader(IpcContext ipcContext, Stream stream) /// /// 当收到消息时触发 /// - public event EventHandler? MessageReceived; + public event EventHandler? MessageReceived; /// /// 当有对方连接时触发 @@ -82,22 +87,49 @@ public async Task RunAsync() { var ipcMessageResult = await IpcMessageConverter.ReadAsync(Stream, IpcConfiguration.MessageHeader, - IpcConfiguration.SharedArrayPool); + IpcConfiguration.SharedArrayPool).ConfigureAwait(false); + + if (ipcMessageResult.IsEndOfStream) + { + IpcContext.Logger.Error($"对方已关闭"); + + OnPeerConnectBroke(new PeerConnectionBrokenArgs()); + return; + } - DispatchMessage(ipcMessageResult); + DispatchMessage(ipcMessageResult.Result); } catch (EndOfStreamException) { - // 对方关闭了 - // [断开某个进程 使用大量CPU在读取 · Issue #15 · dotnet-campus/dotnetCampus.Ipc](https://github.com/dotnet-campus/dotnetCampus.Ipc/issues/15 ) - IpcContext.Logger.Error($"对方已关闭"); +#if DEBUG + // 理论上不会再抛此异常 + if (Debugger.IsAttached) + { + Debugger.Break(); + } +#endif + //// 对方关闭了 + //// [断开某个进程 使用大量CPU在读取 · Issue #15 · dotnet-campus/dotnetCampus.Ipc](https://github.com/dotnet-campus/dotnetCampus.Ipc/issues/15 ) + //IpcContext.Logger.Error($"对方已关闭"); - OnPeerConnectBroke(new PeerConnectionBrokenArgs()); - return; + //OnPeerConnectBroke(new PeerConnectionBrokenArgs()); + //return; + + throw; } catch (Exception e) { - IpcContext.Logger.Error(e); + if (_isDisposed && e is ObjectDisposedException) + { + // 符合预期 + // A 线程调用 Dispose 方法释放 Stream 属性 + // B 线程刚好正在读取内容 + // 此时将会在 IpcMessageConverter 收到 ObjectDisposedException 异常 + } + else + { + IpcContext.Logger.Error(e); + } } } } @@ -115,6 +147,7 @@ private void DispatchMessage(IpcMessageResult ipcMessageResult) if (!success) { // 没有成功哇 + var tracker = CriticalTrackReceiveCore(ipcMessageResult, "接收消息未成功"); return; } @@ -122,8 +155,8 @@ private void DispatchMessage(IpcMessageResult ipcMessageResult) if (ipcMessageCommandType.HasFlag(IpcMessageCommandType.PeerRegister)) { - var isPeerRegisterMessage = - IpcContext.PeerRegisterProvider.TryParsePeerRegisterMessage(stream, out var peerName); + var isPeerRegisterMessage = IpcContext.PeerRegisterProvider.TryParsePeerRegisterMessage(stream, out var peerName); + var tracker = CriticalTrackReceiveCore(ipcMessageResult, peerName); if (IsConnected) { @@ -161,9 +194,10 @@ private void DispatchMessage(IpcMessageResult ipcMessageResult) // 只有业务的才能发给上层 else if (ipcMessageCommandType.HasFlag(IpcMessageCommandType.Business)) { + var tracker = CriticalTrackReceiveCore(ipcMessageResult, "无法识别的端"); if (IsConnected) { - OnMessageReceived(new PeerMessageArgs(PeerName, stream, ipcMessageContext.Ack, ipcMessageCommandType)); + OnMessageReceived(new PeerStreamMessageArgs(ipcMessageContext, PeerName, stream, ipcMessageContext.Ack, ipcMessageCommandType)); } else { @@ -172,12 +206,27 @@ private void DispatchMessage(IpcMessageResult ipcMessageResult) } else { + var tracker = CriticalTrackReceiveCore(ipcMessageResult, "无法识别的消息"); // 不知道这是啥消息哇 // 但是更新一下 ack 意思一下还可以 OnAckReceived(new AckArgs(PeerName, ipcMessageContext.Ack)); } } + private new IpcMessageTracker CriticalTrackReceiveCore(IpcMessageResult result, string remotePeerName) + { + var tracker = new IpcMessageTracker( + IpcContext.PipeName, + remotePeerName, + result.IpcMessageContext, + "DispatchMessage", + IpcContext.Logger); + tracker.CriticalStep("ReceiveCore", + result.IpcMessageContext.Ack, + new IpcMessageBody(result.IpcMessageContext.MessageBuffer, 0, (int) result.IpcMessageContext.MessageLength)); + return tracker; + } + #if false // 下面是注释的代码 private async Task WaitForConnectionAsync() { @@ -331,7 +380,7 @@ private void OnAckReceived(AckArgs e) AckReceived?.Invoke(this, e); } - private void OnMessageReceived(PeerMessageArgs e) + private void OnMessageReceived(PeerStreamMessageArgs e) { MessageReceived?.Invoke(this, e); } diff --git a/src/dotnetCampus.Ipc/IpcClientProvider.cs b/src/dotnetCampus.Ipc/IpcClientProvider.cs deleted file mode 100644 index be6fc37e..00000000 --- a/src/dotnetCampus.Ipc/IpcClientProvider.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Diagnostics; -using System.Reflection; -using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Context; -using dotnetCampus.Ipc.Utils; - -namespace dotnetCampus.Ipc -{ - public class IpcGetObjectRequest - { - public IpcSerializableType ObjectType { set; get; } - } - - public class IpcRequestManager - { - public IPeerProxy PeerProxy { set; get; } = null!; - - public IIpcObjectSerializer IpcObjectSerializer { set; get; } = new IpcObjectJsonSerializer(); - - public Task GetResponseAsync(TRequest request) - { - throw new NotImplementedException(); - } - } - - - - /// - /// 提供客户端使用的方法,可以拿到服务器端的对象 - /// - /// 在服务器端将会维护一个对象池,可以选的是一个对象可以作为单例或者作为每次访问返回新的对象 - /// 如果是每次访问都返回的新的对象,就需要将这个对象发生过去的时候加上对象的标识,之后客户端调用的时候,就可以 - /// 使用这个标识拿到对应的对象 - public class IpcClientProvider - { - /// - /// 从远程获取到对象 - /// - /// - /// - public Task GetObjectAsync() - { - var type = typeof(T); - var ipcSerializableType = new IpcSerializableType(type); - var ipcGetObjectRequest = new IpcGetObjectRequest() - { - ObjectType = ipcSerializableType - }; - - throw new NotImplementedException(); - } - - public IpcRequestManager IpcRequestManager { set; get; } = new IpcRequestManager(); - - - - public IPeerProxy PeerProxy { set; get; } = null!; - - public T GetObject() - { -#if NETCOREAPP - var obj = DispatchProxy.Create>(); - var ipcProxy = obj as IpcProxy; - Debug.Assert(ipcProxy != null); - ipcProxy!.IpcClientProvider = this; - return obj; -#endif - // 还需要加上 NET45 使用的透明代理 - throw new NotImplementedException(); - } - } -} diff --git a/src/dotnetCampus.Ipc/IpcMessageCommandType.cs b/src/dotnetCampus.Ipc/IpcMessageCommandType.cs new file mode 100644 index 00000000..865e1d43 --- /dev/null +++ b/src/dotnetCampus.Ipc/IpcMessageCommandType.cs @@ -0,0 +1,79 @@ +using System; + +namespace dotnetCampus.Ipc +{ + /// + /// 用于作为命令类型,用于框架的命令和业务的命令 + /// + [Flags] + internal enum IpcMessageCommandType : short + { + /// + /// 向对方服务器注册 + /// + PeerRegister = -1, + + /* + /// + /// 发送回复信息 + /// + SendAck = 0B0010, + + /// + /// 发送回复信息,同时向对方服务器注册 + /// + SendAckAndRegisterToPeer = PeerRegister | SendAck, + */ + + /// + /// 业务层的消息 + /// + /// 所有大于 0 的都是业务层消息 + Business = 1, + + /// + /// 请求消息,业务层消息。 + /// + RequestMessage = (1 << 1) | Business, + + /// + /// 响应消息,业务层消息。 + /// + ResponseMessage = (1 << 2) | Business, + + /// + /// 请求的细分类型,IPC 框架无法识别和处理此消息体。 + /// + RawRequestMessage = (1 << 3) | RequestMessage, + + /// + /// 响应的细分类型,IPC 框架无法识别和处理此消息体。 + /// + RawResponseMessage = (1 << 3) | ResponseMessage, + + /// + /// 请求的细分类型,IPC 框架知道此消息体是可被处理的字符串。 + /// + StringRequestMessage = (1 << 4) | RequestMessage, + + /// + /// 响应的细分类型,IPC 框架知道此消息体是可被处理的字符串。 + /// + StringResponseMessage = (1 << 4) | ResponseMessage, + + /// + /// 请求的细分类型,IPC 框架知道此消息体是可被处理的 .NET 对象。 + /// + ObjectRequestMessage = (1 << 5) | RequestMessage, + + /// + /// 响应的细分类型,IPC 框架知道此消息体是可被处理的 .NET 对象。 + /// + ObjectResponseMessage = (1 << 5) | ResponseMessage, + + /// + /// 其他消息。 + /// + Unknown = short.MaxValue, + } +} diff --git a/src/dotnetCampus.Ipc/IpcMessageWriter.cs b/src/dotnetCampus.Ipc/IpcMessageWriter.cs new file mode 100644 index 00000000..df431004 --- /dev/null +++ b/src/dotnetCampus.Ipc/IpcMessageWriter.cs @@ -0,0 +1,54 @@ +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc +{ + /// + /// 提供消息的写入方法 + /// + public class IpcMessageWriter : IRawMessageWriter + { + /// + /// 创建提供消息的写入方法 + /// + /// 实际用来写入的方法 + public IpcMessageWriter(IRawMessageWriter messageWriter) + { + RawWriter = messageWriter; + } + + private IRawMessageWriter RawWriter { get; } + + /// + public Task WriteMessageAsync(byte[] buffer, int offset, int count, [CallerMemberName] string tag = "") + { + return RawWriter.WriteMessageAsync(buffer, offset, count, tag); + } + + /// + /// 向对方发送消息 + /// + /// 字符串消息,将会被使用Utf-8编码转换然后发送 + /// + /// + public Task WriteMessageAsync(string message, string? tag = null) + { + tag ??= message; + var messageBuffer = Encoding.UTF8.GetBytes(message); + return WriteMessageAsync(messageBuffer, 0, messageBuffer.Length, tag); + } + + /// + /// 向对方发送消息 + /// + /// 字符串消息,将会被使用Utf-8编码转换然后发送 + /// + public Task WriteMessageAsync(IpcMessage message) + { + return WriteMessageAsync(message.Body.Buffer, message.Body.Start, message.Body.Length, message.Tag); + } + } +} diff --git a/src/dotnetCampus.Ipc/IpcProxy.cs b/src/dotnetCampus.Ipc/IpcProxy.cs deleted file mode 100644 index a6fdae44..00000000 --- a/src/dotnetCampus.Ipc/IpcProxy.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Context; -using dotnetCampus.Ipc.Utils; - -namespace dotnetCampus.Ipc -{ - -#if !NETCOREAPP - // todo 后续需要放在 .NET Core 程序集,因此这个库后续理论上是需要支持 .NET Framework 的 - public abstract class DispatchProxy - { - protected abstract object Invoke(MethodInfo targetMethod, object[] args); - } -#endif - - public class IpcProxy : DispatchProxy - { - /// - /// 用来标识服务器端的对象 - /// - public ulong ObjectId { set; get; } - - public IIpcObjectSerializer IpcObjectSerializer { set; get; } - - public IpcClientProvider IpcClientProvider { set; get; } = null!; - - protected override object Invoke(MethodInfo targetMethod, object[] args) - { - var actualReturnType = GetAndCheckActualReturnType(targetMethod.ReturnType); - - var parameters = targetMethod.GetParameters(); - - var parameterTypes = parameters.Select(p => new IpcRequestParameterType(p.ParameterType)).ToArray(); - - var parameterList = new List(parameterTypes.Length); - - for (var i = 0; i < parameterTypes.Length; i++) - { - var ipcRequestParameter = new IpcRequestParameter() - { - ParameterType = parameterTypes[i], - Value = args[i] - }; - - parameterList.Add(ipcRequestParameter); - } - - var genericTypes = targetMethod.GetGenericArguments(); - - var genericArgumentList = genericTypes.Select(type => new IpcRequestParameterType(type)).Cast().ToList(); - - var ipcRequest = new IpcRequest() - { - MethodName = targetMethod.Name, - ParameterList = parameterList, - GenericArgumentList = genericArgumentList, - ReturnType = new IpcSerializableType(actualReturnType), - ObjectType = new IpcSerializableType(typeof(T)), - ObjectId = ObjectId, - }; - - //IpcResponse response = await GetResponseAsync(ipcRequest); - ////Task - - //TaskCompletionSource t = new TaskCompletionSource(); - - //int n = await foo.FooAsync(); - //var re = IpcObjectSerializer.Serialize(ipcRequest); - - // 此方法还没完成,等待下一次实现,有技术实现问题 - throw new NotImplementedException(); - } - - private Type GetAndCheckActualReturnType(Type returnType) - { - if (returnType == typeof(Task)) - { - return typeof(void); - } - else if (returnType.BaseType == typeof(Task)) - { - if (returnType.Name == "Task`1") - { - if (returnType.GenericTypeArguments.Length == 1) - { - return returnType.GenericTypeArguments[0]; - } - } - } - - throw new ArgumentException($"方法返回值只能是 Task 或 Task 泛型"); - } - } -} diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/Ack.cs b/src/dotnetCampus.Ipc/Messages/Ack.cs similarity index 95% rename from src/dotnetCampus.Ipc.PipeCore/Context/Ack.cs rename to src/dotnetCampus.Ipc/Messages/Ack.cs index 4565bfb3..d465010d 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/Ack.cs +++ b/src/dotnetCampus.Ipc/Messages/Ack.cs @@ -1,4 +1,4 @@ -namespace dotnetCampus.Ipc.PipeCore.Context +namespace dotnetCampus.Ipc.Messages { /// /// 用于作为消息的回复编号 diff --git a/src/dotnetCampus.Ipc/Messages/CoreMessageType.cs b/src/dotnetCampus.Ipc/Messages/CoreMessageType.cs new file mode 100644 index 00000000..1705c20f --- /dev/null +++ b/src/dotnetCampus.Ipc/Messages/CoreMessageType.cs @@ -0,0 +1,32 @@ +using System; + +namespace dotnetCampus.Ipc.Messages +{ + /// + /// 在 IPC 框架内部用来标识消息的类型。 + /// 此枚举是内部的,不要求每一套 IPC 实现都完全实现这里的所有类型的消息,因此这里可以提供目前能识别的所有类型消息的完全集合。 + /// + [Flags] + internal enum CoreMessageType + { + /// + /// 框架内部必须处理的消息或回应。 + /// + NotMessageBody = 0, + + /// + /// 无特殊标识的消息。IPC 框架无法准确得知此消息的具体内容。 + /// + Raw = 1 << 0, + + /// + /// 标记为字符串的消息。IPC 框架知道消息体是一个字符串。 + /// + String = 1 << 1, + + /// + /// 标记为 .NET 对象的消息。IPC 框架知道消息体是用 JSON 序列化过的 .NET 对象。 + /// + JsonObject = 1 << 2, + } +} diff --git a/src/dotnetCampus.Ipc.Abstractions/IIpcHandleRequestMessageResult.cs b/src/dotnetCampus.Ipc/Messages/IIpcResponseMessage.cs similarity index 54% rename from src/dotnetCampus.Ipc.Abstractions/IIpcHandleRequestMessageResult.cs rename to src/dotnetCampus.Ipc/Messages/IIpcResponseMessage.cs index 46a27843..c8ca9db9 100644 --- a/src/dotnetCampus.Ipc.Abstractions/IIpcHandleRequestMessageResult.cs +++ b/src/dotnetCampus.Ipc/Messages/IIpcResponseMessage.cs @@ -1,13 +1,13 @@ -namespace dotnetCampus.Ipc.Abstractions +namespace dotnetCampus.Ipc.Messages { /// /// 处理客户端请求的结果 /// - public interface IIpcHandleRequestMessageResult + public interface IIpcResponseMessage { /// /// 返回给对方的信息 /// - IpcRequestMessage ReturnMessage { get; } + IpcMessage ResponseMessage { get; } } } diff --git a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestMessage.cs b/src/dotnetCampus.Ipc/Messages/IpcClientRequestMessage.cs similarity index 65% rename from src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestMessage.cs rename to src/dotnetCampus.Ipc/Messages/IpcClientRequestMessage.cs index 72edefae..f039e720 100644 --- a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestMessage.cs +++ b/src/dotnetCampus.Ipc/Messages/IpcClientRequestMessage.cs @@ -1,12 +1,12 @@ using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore.Context; -namespace dotnetCampus.Ipc.PipeCore.IpcPipe +using dotnetCampus.Ipc.Context; + +namespace dotnetCampus.Ipc.Messages { class IpcClientRequestMessage { - public IpcClientRequestMessage(IpcBufferMessageContext ipcBufferMessageContext, Task task, IpcClientRequestMessageId messageId) + public IpcClientRequestMessage(IpcBufferMessageContext ipcBufferMessageContext, Task task, IpcClientRequestMessageId messageId) { IpcBufferMessageContext = ipcBufferMessageContext; Task = task; @@ -18,7 +18,7 @@ public IpcClientRequestMessage(IpcBufferMessageContext ipcBufferMessageContext, /// /// 用于等待消息被对方回复完成 /// - public Task Task { get; } + public Task Task { get; } public IpcClientRequestMessageId MessageId { get; } } diff --git a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestMessageId.cs b/src/dotnetCampus.Ipc/Messages/IpcClientRequestMessageId.cs similarity index 93% rename from src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestMessageId.cs rename to src/dotnetCampus.Ipc/Messages/IpcClientRequestMessageId.cs index b339d552..bf8f651e 100644 --- a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/Context_/IpcClientRequestMessageId.cs +++ b/src/dotnetCampus.Ipc/Messages/IpcClientRequestMessageId.cs @@ -1,4 +1,4 @@ -namespace dotnetCampus.Ipc.PipeCore.IpcPipe +namespace dotnetCampus.Ipc.Messages { /// /// 客户端请求的信息号 diff --git a/src/dotnetCampus.Ipc/Messages/IpcMessage.cs b/src/dotnetCampus.Ipc/Messages/IpcMessage.cs new file mode 100644 index 00000000..d272b497 --- /dev/null +++ b/src/dotnetCampus.Ipc/Messages/IpcMessage.cs @@ -0,0 +1,73 @@ +using System.Diagnostics; + +namespace dotnetCampus.Ipc.Messages +{ + /// + /// 可在 IPC 框架中传输(收、发)的消息。 + /// + public readonly struct IpcMessage + { + /// + /// 创建一条可在 IPC 框架中传输的消息。 + /// + /// 请标记此消息用于在调试过程中追踪。 + /// IPC 消息的具体内容。 + [DebuggerStepThrough] + public IpcMessage(string tag, IpcMessageBody body) + { + Tag = tag; + Body = body; + CoreMessageType = CoreMessageType.Raw; + } + + /// + /// 创建一条可在 IPC 框架中传输的消息。 + /// + /// 请标记此消息用于在调试过程中追踪。 + /// IPC 消息的具体内容。 + [DebuggerStepThrough] + public IpcMessage(string tag, byte[] data) : this(tag, new IpcMessageBody(data)) + { + } + + /// + /// 创建一条可在 IPC 框架中传输的消息。 + /// + /// 请标记此消息用于在调试过程中追踪。 + /// IPC 消息的具体内容。 + /// 由 IPC 框架传入,用以标记此消息可被 IPC 框架识别和处理的类型。 + [DebuggerStepThrough] + internal IpcMessage(string tag, IpcMessageBody body, CoreMessageType coreMessageType) + { + Tag = tag; + Body = body; + CoreMessageType = coreMessageType; + } + + /// + /// 创建一条可在 IPC 框架中传输的消息。 + /// + /// 请标记此消息用于在调试过程中追踪。 + /// IPC 消息的具体内容。 + /// 由 IPC 框架传入,用以标记此消息可被 IPC 框架识别和处理的类型。 + [DebuggerStepThrough] + internal IpcMessage(string tag, byte[] data, CoreMessageType coreMessageType) : this(tag, new IpcMessageBody(data), coreMessageType) + { + } + + /// + /// 用于在调试过程中追踪此 IPC 消息。 + /// + public string Tag { get; } + + /// + /// IPC 消息的具体内容。 + /// + public IpcMessageBody Body { get; } + + /// + /// 标记此消息可被 IPC 框架识别和处理的类型。 + /// + internal CoreMessageType CoreMessageType { get; } + } +} diff --git a/src/dotnetCampus.Ipc/Messages/IpcMessageBody.cs b/src/dotnetCampus.Ipc/Messages/IpcMessageBody.cs new file mode 100644 index 00000000..42da36fc --- /dev/null +++ b/src/dotnetCampus.Ipc/Messages/IpcMessageBody.cs @@ -0,0 +1,77 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace dotnetCampus.Ipc.Messages +{ + /// + /// 表示一段 Ipc 消息内容 + /// + public readonly struct IpcMessageBody + { + /// + /// 创建一段 Ipc 消息内容 + /// + /// + [DebuggerStepThrough] + public IpcMessageBody(byte[] buffer) + { + Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + Start = 0; + Length = buffer.Length; + } + + /// + /// 创建一段 Ipc 消息内容 + /// + /// + /// + /// + [DebuggerStepThrough] + public IpcMessageBody(byte[] buffer, int start, int length) + { + if (start < 0 || start > buffer.Length - 1) + { + throw new ArgumentOutOfRangeException(nameof(start), "消息体长度必须大于 0。如果此消息来自发送方,请检查是否发送了消息体长度为 0 的消息。"); + } + + if (length < 0 || start + length > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + Start = start; + Length = length; + } + + /// + /// 缓存数据 + /// + public byte[] Buffer { get; } + + /// + /// 缓存数据的起始点 + /// + public int Start { get; } + + /// + /// 数据长度 + /// + public int Length { get; } + } + + /// + /// 给 的扩展 + /// + public static class IpcMessageBodyExtensions + { + /// + /// 转换为 对象 + /// + /// + /// + public static MemoryStream ToMemoryStream(this IpcMessageBody message) => + new MemoryStream(message.Buffer, message.Start, message.Length, false); + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageContext.cs b/src/dotnetCampus.Ipc/Messages/IpcMessageContext.cs similarity index 86% rename from src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageContext.cs rename to src/dotnetCampus.Ipc/Messages/IpcMessageContext.cs index a7e107d1..43932871 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageContext.cs +++ b/src/dotnetCampus.Ipc/Messages/IpcMessageContext.cs @@ -1,6 +1,6 @@ -using dotnetCampus.Ipc.PipeCore.Utils; +using dotnetCampus.Ipc.Utils.Buffers; -namespace dotnetCampus.Ipc.PipeCore.Context +namespace dotnetCampus.Ipc.Messages { internal readonly struct IpcMessageContext { diff --git a/src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageResult.cs b/src/dotnetCampus.Ipc/Messages/IpcMessageResult.cs similarity index 95% rename from src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageResult.cs rename to src/dotnetCampus.Ipc/Messages/IpcMessageResult.cs index 6ed79bfb..615a5d9e 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Context/IpcMessageResult.cs +++ b/src/dotnetCampus.Ipc/Messages/IpcMessageResult.cs @@ -1,4 +1,4 @@ -namespace dotnetCampus.Ipc.PipeCore.Context +namespace dotnetCampus.Ipc.Messages { class IpcMessageResult { diff --git a/src/dotnetCampus.Ipc/Messages/KnownResponseMessages.cs b/src/dotnetCampus.Ipc/Messages/KnownResponseMessages.cs new file mode 100644 index 00000000..d5799b89 --- /dev/null +++ b/src/dotnetCampus.Ipc/Messages/KnownResponseMessages.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace dotnetCampus.Ipc.Messages +{ + /// + /// IPC 框架已知的 IPC 响应消息。 + /// + public static class KnownIpcResponseMessages + { + /// + /// 不会处理此种类型的 IPC 消息,因此返回了一个“不会处理”响应。 + /// + public static IIpcResponseMessage CannotHandle { get; } = new NamedIpcResponseMessage(nameof(CannotHandle)); + + [DebuggerDisplay("IpcResponseMessage.{" + nameof(Name) + ",nq}")] + private sealed class NamedIpcResponseMessage : IIpcResponseMessage, IEquatable + { + public NamedIpcResponseMessage(string name) + { + Name = name; + ResponseMessage = new(name, new byte[0]); + } + + internal string Name { get; } + + public IpcMessage ResponseMessage { get; } + + public override bool Equals(object? obj) + { + return obj is NamedIpcResponseMessage message && Equals(message); + } + + public bool Equals(NamedIpcResponseMessage? other) + { + return other is not null && Name == other.Name; + } + + public override int GetHashCode() + { + return 539060726 + EqualityComparer.Default.GetHashCode(Name); + } + + public static bool operator ==(NamedIpcResponseMessage left, NamedIpcResponseMessage right) + { + return left.Equals(right); + } + + public static bool operator !=(NamedIpcResponseMessage left, NamedIpcResponseMessage right) + { + return !(left == right); + } + } + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcClientService.cs b/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs similarity index 61% rename from src/dotnetCampus.Ipc.PipeCore/Core_/IpcClientService.cs rename to src/dotnetCampus.Ipc/Pipes/IpcClientService.cs index 4d6bd854..8a556c55 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcClientService.cs +++ b/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs @@ -4,19 +4,22 @@ using System.Runtime.CompilerServices; using System.Security.Principal; using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.Utils; -using dotnetCampus.Ipc.PipeCore.Utils.Extensions; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Diagnostics; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Extensions; +using dotnetCampus.Ipc.Utils.Logging; using dotnetCampus.Threading; -namespace dotnetCampus.Ipc.PipeCore +namespace dotnetCampus.Ipc.Pipes { /// /// 管道的客户端,用于发送消息 /// /// 采用两个半工的管道做到双向通讯,这里的管道客户端用于发送 - public class IpcClientService : IMessageWriter, IDisposable, IClientMessageWriter + public class IpcClientService : IRawMessageWriter, IDisposable, IClientMessageWriter { /// /// 连接其他端,用来发送 @@ -37,7 +40,7 @@ private async Task DoTask(List> list) { try { - await func(); + await func().ConfigureAwait(false); } catch (Exception e) { @@ -46,7 +49,11 @@ private async Task DoTask(List> list) } } - private NamedPipeClientStream NamedPipeClientStream { set; get; } = null!; + private TaskCompletionSource? _namedPipeClientStreamTaskCompletionSource; + + private Task NamedPipeClientStreamTask => _namedPipeClientStreamTaskCompletionSource is null + ? Task.FromResult(null!) + : _namedPipeClientStreamTaskCompletionSource.Task; internal AckManager AckManager => IpcContext.AckManager; @@ -75,14 +82,17 @@ public async Task Start(bool shouldRegisterToPeer = true) { var namedPipeClientStream = new NamedPipeClientStream(".", PeerName, PipeDirection.Out, PipeOptions.None, TokenImpersonationLevel.Impersonation); + _namedPipeClientStreamTaskCompletionSource = new TaskCompletionSource(); #if NETCOREAPP await namedPipeClientStream.ConnectAsync(); #else // 在 NET45 没有 ConnectAsync 方法 await Task.Run(namedPipeClientStream.Connect); #endif - - NamedPipeClientStream = namedPipeClientStream; + if (!_namedPipeClientStreamTaskCompletionSource.Task.IsCompleted) + { + _namedPipeClientStreamTaskCompletionSource.SetResult(namedPipeClientStream); + } if (shouldRegisterToPeer) { @@ -93,11 +103,14 @@ public async Task Start(bool shouldRegisterToPeer = true) private async Task RegisterToPeer() { - Logger.Debug($"[{nameof(IpcClientService)}] StartRegisterToPeer PipeName={IpcContext.PipeName}"); + Logger.Trace($"[{nameof(IpcClientService)}] StartRegisterToPeer PipeName={IpcContext.PipeName}"); // 注册自己 var peerRegisterMessage = PeerRegisterProvider.BuildPeerRegisterMessage(IpcContext.PipeName); - await WriteMessageAsync(peerRegisterMessage); + var peerRegisterMessageTracker = new IpcMessageTracker( + IpcContext.PipeName, PeerName, peerRegisterMessage, + $"PeerRegisterMessage PipeName={IpcContext.PipeName}", IpcContext.Logger); + await WriteMessageAsync(peerRegisterMessageTracker); } /// @@ -114,21 +127,71 @@ public void Stop() /// /// 框架层使用的 /// - internal async Task WriteMessageAsync(IpcBufferMessageContext ipcBufferMessageContext) + internal async Task WriteMessageAsync(IpcMessageTracker tracker) + { + await DoubleBufferTask.AddTaskAsync(WriteMessageAsyncInner).ConfigureAwait(false); + + async Task WriteMessageAsyncInner() + { + var stream = await NamedPipeClientStreamTask.ConfigureAwait(false); + + // 追踪、校验消息。 + var ack = AckManager.GetAck(); + tracker.Debug("IPC start writing..."); + tracker.CriticalStep("SendCore", ack, tracker.Message.IpcBufferMessageList); + + // 发送消息。 + await IpcMessageConverter.WriteAsync + ( + stream, + IpcConfiguration.MessageHeader, + ack, + tracker.Message + ).ConfigureAwait(false); + await stream.FlushAsync().ConfigureAwait(false); + + // 追踪消息。 + tracker.Debug("IPC finish writing."); + } + } + + /// + /// 向服务端发送消息 + /// + /// + /// + /// + /// 业务层使用的 + /// + internal async Task WriteMessageAsync(IpcMessageTracker tracker) { await DoubleBufferTask.AddTaskAsync(WriteMessageAsyncInner); async Task WriteMessageAsyncInner() { + var stream = await NamedPipeClientStreamTask.ConfigureAwait(false); + + // 追踪、校验消息。 + var ack = AckManager.GetAck(); + tracker.Debug("IPC start writing..."); + tracker.CriticalStep("SendCore", ack, tracker.Message); + + // 发送消息。 await IpcMessageConverter.WriteAsync ( - NamedPipeClientStream, + stream, IpcConfiguration.MessageHeader, AckManager.GetAck(), - ipcBufferMessageContext, - Logger + // 表示这是业务层的消息 + IpcMessageCommandType.Business, + tracker.Message.Buffer, + tracker.Message.Start, + tracker.Message.Length ); - await NamedPipeClientStream.FlushAsync(); + await stream.FlushAsync().ConfigureAwait(false); + + // 追踪消息。 + tracker.Debug("IPC finish writing."); } } @@ -138,21 +201,23 @@ await IpcMessageConverter.WriteAsync /// /// /// - /// 这一次写入的是什么内容,用于调试 + /// 这一次写入的是什么内容,用于调试 /// /// /// 业务层使用的 /// public async Task WriteMessageAsync(byte[] buffer, int offset, int count, - [CallerMemberName] string summary = null!) + [CallerMemberName] string tag = null!) { await DoubleBufferTask.AddTaskAsync(WriteMessageAsyncInner); async Task WriteMessageAsyncInner() { + var currentTag = tag; + var stream = await NamedPipeClientStreamTask.ConfigureAwait(false); await IpcMessageConverter.WriteAsync ( - NamedPipeClientStream, + stream, IpcConfiguration.MessageHeader, AckManager.GetAck(), // 表示这是业务层的消息 @@ -160,14 +225,12 @@ await IpcMessageConverter.WriteAsync buffer, offset, count, - summary, - Logger + currentTag ); - await NamedPipeClientStream.FlushAsync(); + await stream.FlushAsync().ConfigureAwait(false); } } - /* private async Task QueueWriteAsync(Func task, string summary) { @@ -228,13 +291,23 @@ public void Dispose() } IsDisposed = true; - NamedPipeClientStream.Dispose(); + + if (NamedPipeClientStreamTask.IsCompleted) + { + NamedPipeClientStreamTask.Result.Dispose(); + } + DoubleBufferTask.Finish(); } private bool IsDisposed { set; get; } - Task IClientMessageWriter.WriteMessageAsync(in IpcBufferMessageContext ipcBufferMessageContext) => - WriteMessageAsync(ipcBufferMessageContext); + Task IClientMessageWriter.WriteMessageAsync(in IpcBufferMessageContext ipcBufferMessageContext) + { + var peerRegisterMessageTracker = new IpcMessageTracker( + IpcContext.PipeName, PeerName, ipcBufferMessageContext, + $"PeerRegisterMessage PipeName={IpcContext.PipeName}", IpcContext.Logger); + return WriteMessageAsync(peerRegisterMessageTracker); + } } } diff --git a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageManagerBase.cs b/src/dotnetCampus.Ipc/Pipes/IpcMessageManagerBase.cs similarity index 69% rename from src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageManagerBase.cs rename to src/dotnetCampus.Ipc/Pipes/IpcMessageManagerBase.cs index 9eb7b7fb..19ab35d1 100644 --- a/src/dotnetCampus.Ipc.PipeCore/IpcPipe/IpcMessageManager_/IpcMessageManagerBase.cs +++ b/src/dotnetCampus.Ipc/Pipes/IpcMessageManagerBase.cs @@ -1,16 +1,17 @@ using System; using System.IO; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore.Context; -namespace dotnetCampus.Ipc.PipeCore.IpcPipe +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Extensions; + +namespace dotnetCampus.Ipc.Pipes { class IpcMessageManagerBase { // 为什么将请求和响应的消息封装都放在一个类里面?这是为了方便更改,和调试 // 如果放在两个类或两个文件里面,也许就不好调试对应关系 - protected static IpcBufferMessageContext CreateResponseMessageInner(IpcClientRequestMessageId messageId, IpcRequestMessage response) + protected static IpcBufferMessageContext CreateResponseMessageInner(IpcClientRequestMessageId messageId, in IpcMessage response) { /* * MessageHeader @@ -20,19 +21,19 @@ protected static IpcBufferMessageContext CreateResponseMessageInner(IpcClientReq */ var currentMessageIdByteList = BitConverter.GetBytes(messageId.MessageIdValue); - var responseMessageLengthByteList = BitConverter.GetBytes(response.RequestMessage.Count); + var responseMessageLengthByteList = BitConverter.GetBytes(response.Body.Length); return new IpcBufferMessageContext ( - response.Summary, - IpcMessageCommandType.ResponseMessage, - new IpcBufferMessage(ResponseMessageHeader), - new IpcBufferMessage(currentMessageIdByteList), - new IpcBufferMessage(responseMessageLengthByteList), - response.RequestMessage + response.Tag, + IpcMessageCommandType.ResponseMessage | response.CoreMessageType.AsMessageCommandTypeFlags(), + new IpcMessageBody(ResponseMessageHeader), + new IpcMessageBody(currentMessageIdByteList), + new IpcMessageBody(responseMessageLengthByteList), + response.Body ); } - protected static IpcBufferMessageContext CreateRequestMessageInner(in IpcRequestMessage request, ulong currentMessageId) + protected static IpcBufferMessageContext CreateRequestMessageInner(in IpcMessage request, ulong currentMessageId) { /* * MessageHeader @@ -42,16 +43,16 @@ protected static IpcBufferMessageContext CreateRequestMessageInner(in IpcRequest */ var currentMessageIdByteList = BitConverter.GetBytes(currentMessageId); - var requestMessageLengthByteList = BitConverter.GetBytes(request.RequestMessage.Count); + var requestMessageLengthByteList = BitConverter.GetBytes(request.Body.Length); return new IpcBufferMessageContext ( - request.Summary, - IpcMessageCommandType.RequestMessage, - new IpcBufferMessage(RequestMessageHeader), - new IpcBufferMessage(currentMessageIdByteList), - new IpcBufferMessage(requestMessageLengthByteList), - request.RequestMessage + request.Tag, + IpcMessageCommandType.RequestMessage | request.CoreMessageType.AsMessageCommandTypeFlags(), + new IpcMessageBody(RequestMessageHeader), + new IpcMessageBody(currentMessageIdByteList), + new IpcMessageBody(requestMessageLengthByteList), + request.Body ); } diff --git a/src/dotnetCampus.Ipc/Pipes/IpcMessageRequestManager.cs b/src/dotnetCampus.Ipc/Pipes/IpcMessageRequestManager.cs new file mode 100644 index 00000000..69e8c461 --- /dev/null +++ b/src/dotnetCampus.Ipc/Pipes/IpcMessageRequestManager.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Exceptions; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Pipes +{ + /// + /// 请求管理 + /// + /// 这是用来期待发送一条消息之后,在对方的业务层能有回复的消息 + /// 这是服务器-客户端模型 + /// 客户端向服务器发起请求,服务器端业务层处理之后返回响应信息 + /// 通过调用 方法创建出请求消息 + /// 然后将此消息的 通过现有 发送到服务器端。同时客户端可以使用 进行等待 + /// 服务器端接收到 的内容,将会在 事件触发,这个事件将会带上 参数 + /// 在服务器端处理完成之后,底层的方法是通过调用 方法创建响应消息,通过 发送给客户端 + /// 客户端收到了服务器端的响应信息,将会释放 任务,客户端从 可以拿到服务器端的返回值 + /// + class IpcMessageRequestManager : IpcMessageManagerBase + { + /// + /// 等待响应的数量 + /// + public int WaitingResponseCount + { + get + { + lock (Locker) + { + return TaskList.Count; + } + } + } + + public IpcClientRequestMessage CreateRequestMessage(IpcMessage request) + { + ulong currentMessageId; + var task = new TaskCompletionSource(); + lock (Locker) + { + currentMessageId = CurrentMessageId; + // 在超过 ulong.MaxValue 之后,将会是 0 这个值 + CurrentMessageId++; + + TaskList[currentMessageId] = task; + } + + var requestMessage = CreateRequestMessageInner(request, currentMessageId); + return new IpcClientRequestMessage(requestMessage, task.Task, new IpcClientRequestMessageId(currentMessageId)); + } + + private Dictionary> TaskList { get; } = + new Dictionary>(); + + /// + /// 收到消息,包括收到别人的请求消息,和收到别人的响应消息 + /// + /// + public void OnReceiveMessage(PeerStreamMessageArgs args) + { + HandleResponse(args); + if (args.Handle) + { + return; + } + + HandleRequest(args); + } + + /// + /// 断开连接之后的炸掉所有的请求任务 + /// + /// 重发?其实会丢失上下文,因此不合适 + /// 如某个业务需要连续发送三条请求消息才能实现 + /// 但是前面两条消息成功,在发送第三条请求时,对方进程退出 + /// 如果让第三条请求重新发送,将会让新的重连的对方收到一条非预期的消息 + /// + /// 然而如果不重发的话,也许如上面例子的三条消息差距很大,前两条消息发送之后 + /// 对方进程挂了,等待一会,才发送第三条消息 + /// 这也是坑 + /// + /// 然而此时也触发了断开的事件,也许此时的业务端可以处理 + public void BreakAllRequestTaskByIpcBroken() + { + List> taskList; + lock (Locker) + { + taskList = TaskList.Select(temp => temp.Value).ToList(); + TaskList.Clear(); + } + + foreach (var taskCompletionSource in taskList) + { + var ipcPeerConnectionBrokenException = new IpcPeerConnectionBrokenException(); + if (taskCompletionSource.TrySetException(ipcPeerConnectionBrokenException)) + { + + } + else + { + // 难道在断开的时候,刚好收到消息了? + } + } + } + + private void HandleRequest(PeerStreamMessageArgs args) + { + if (!args.MessageCommandType.HasFlag(IpcMessageCommandType.RequestMessage)) + { + // 如果没有请求标识,那么返回。 + return; + } + + var message = args.MessageStream; + if (message.Length < RequestMessageHeader.Length + sizeof(ulong)) + { + return; + } + + if (CheckRequestHeader(message)) + { + // 标记在这一级消费 + args.SetHandle(message: nameof(HandleRequest)); + + var binaryReader = new BinaryReader(message); + var messageId = binaryReader.ReadUInt64(); + var requestMessageLength = binaryReader.ReadInt32(); + + var currentPosition = message.Position; + try + { + var requestMessageByteList = binaryReader.ReadBytes(requestMessageLength); + var ipcClientRequestArgs = + new IpcClientRequestArgs(new IpcClientRequestMessageId(messageId), + new IpcMessageBody(requestMessageByteList), + args.MessageCommandType); + OnIpcClientRequestReceived?.Invoke(this, ipcClientRequestArgs); + } + finally + { + message.Position = currentPosition; + } + } + } + + public event EventHandler? OnIpcClientRequestReceived; + + private void HandleResponse(PeerStreamMessageArgs args) + { + if (!args.MessageCommandType.HasFlag(IpcMessageCommandType.ResponseMessage)) + { + // 如果没有命令标识,那么返回 + return; + } + + var message = args.MessageStream; + + if (message.Length < ResponseMessageHeader.Length + sizeof(ulong)) + { + return; + } + + if (CheckResponseHeader(message)) + { + // 标记在这一级消费 + args.SetHandle(message: nameof(HandleResponse)); + + var binaryReader = new BinaryReader(message); + var messageId = binaryReader.ReadUInt64(); + TaskCompletionSource? task = null; + lock (Locker) + { + // 下面这句代码在 .NET 45 不存在,因此不能使用。换成两次调用,反正性能差不多 + //TaskList.Remove(messageId, out task); + if (TaskList.TryGetValue(messageId, out task)) + { + TaskList.Remove(messageId); + } + else + { + return; + } + } + + if (task == null) + { + return; + } + + var responseMessageLength = binaryReader.ReadInt32(); + + var currentPosition = message.Position; + try + { + var responseMessageByteList = binaryReader.ReadBytes(responseMessageLength); + task.SetResult(new IpcMessageBody(responseMessageByteList)); + } + finally + { + message.Position = currentPosition; + } + } + } + + private object Locker => TaskList; + + private ulong CurrentMessageId { set; get; } + } +} diff --git a/src/dotnetCampus.Ipc/Pipes/IpcMessageResponseManager.cs b/src/dotnetCampus.Ipc/Pipes/IpcMessageResponseManager.cs new file mode 100644 index 00000000..ef3aa5ee --- /dev/null +++ b/src/dotnetCampus.Ipc/Pipes/IpcMessageResponseManager.cs @@ -0,0 +1,17 @@ +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Pipes +{ + /// + /// 响应管理器 + /// + /// 这个类还没设计好,和 是重复的 + /// 完全可以删除 + class IpcMessageResponseManager : IpcMessageManagerBase + { + public IpcBufferMessageContext CreateResponseMessage(IpcClientRequestMessageId messageId, + in IpcMessage response) + => CreateResponseMessageInner(messageId, in response); + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcProvider.cs b/src/dotnetCampus.Ipc/Pipes/IpcProvider.cs similarity index 76% rename from src/dotnetCampus.Ipc.PipeCore/Core_/IpcProvider.cs rename to src/dotnetCampus.Ipc/Pipes/IpcProvider.cs index 3b65ac21..f595bb35 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcProvider.cs +++ b/src/dotnetCampus.Ipc/Pipes/IpcProvider.cs @@ -1,13 +1,13 @@ using System; using System.Diagnostics; -using System.IO; -using System.IO.Pipes; using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions.Context; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.Utils; -namespace dotnetCampus.Ipc.PipeCore +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies; +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Utils.Extensions; + +namespace dotnetCampus.Ipc.Pipes { /// /// 对等通讯,每个都是服务器端,每个都是客户端 @@ -15,7 +15,7 @@ namespace dotnetCampus.Ipc.PipeCore /// 这是这个程序集最顶层的类 /// 这里有两个概念,一个是对方,另一个是本地 /// 对方就是其他的开启的Ipc服务的端,可以在相同的进程内。而本地是指此Ipc服务 - public class IpcProvider : IDisposable + public class IpcProvider : IIpcProvider, IDisposable { /// /// 创建对等通讯 @@ -32,11 +32,21 @@ public IpcProvider() : this(Guid.NewGuid().ToString("N")) public IpcProvider(string pipeName, IpcConfiguration? ipcConfiguration = null) { IpcContext = new IpcContext(this, pipeName, ipcConfiguration); - IpcContext.Logger.Debug($"[IpcProvider] 本地服务名 {pipeName}"); + IpcContext.IpcConfiguration.AddFrameworkRequestHandlers(IpcContext.GeneratedProxyJointIpcContext.RequestHandler); + IpcContext.Logger.Trace($"[IpcProvider] 本地服务名 {pipeName}"); + + PeerManager = new PeerManager(this); } - private IpcContext IpcContext { get; } - private PeerManager PeerManager { get; } = new PeerManager(); + /// + public IpcContext IpcContext { get; } + + private PeerManager PeerManager { get; } + + /// + /// 是否启动了 + /// + public bool IsStarted => _ipcServerService != null; /// /// 开启的管道服务端,用于接收消息 @@ -52,7 +62,7 @@ public IpcServerService IpcServerService /// public async void StartServer() { - if (_ipcServerService != null) return; + if (IsStarted) return; var ipcServerService = new IpcServerService(IpcContext); _ipcServerService = ipcServerService; @@ -60,7 +70,7 @@ public async void StartServer() ipcServerService.PeerConnected += NamedPipeServerStreamPoolPeerConnected; // 以下的 Start 是一个循环,不会返回的 - await ipcServerService.Start(); + await ipcServerService.Start().ConfigureAwait(false); } /// @@ -71,9 +81,10 @@ public async void StartServer() private async void NamedPipeServerStreamPoolPeerConnected(object? sender, IpcInternalPeerConnectedArgs e) { // 也许是对方反过来连接 - if (PeerManager.ConnectedServerManagerList.TryGetValue(e.PeerName, out var peerProxy)) + if (PeerManager.TryGetValue(e.PeerName, out var peerProxy)) { - if (peerProxy.IsBroken) + // 如果当前的 Peer 已断开且不需要重新连接,那么重新创建 Peer 反过来连接对方的服务器端 + if (peerProxy.IsBroken && !IpcContext.IpcConfiguration.AutoReconnectPeers) { // 理论上不会进入这个分支,因为如果 IsBroken 将会自动去清理,除非刚好一个断开,然后立刻连接 PeerManager.RemovePeerProxy(peerProxy); @@ -96,7 +107,7 @@ private async Task ConnectBackToPeer(IpcInternalPeerConnectedArgs e) var peerName = e.PeerName; //var receivedAck = e.Ack; - if (PeerManager.ConnectedServerManagerList.TryGetValue(peerName, out _)) + if (PeerManager.TryGetValue(peerName, out _)) { // 预期不会进入此分支,也就是之前没有连接过才对 Debug.Assert(false, "对方连接之前没有记录对方"); @@ -104,17 +115,20 @@ private async Task ConnectBackToPeer(IpcInternalPeerConnectedArgs e) else { // 无须再次启动本地的服务器端,因为有对方连接过来,此时一定开启了本地的服务器端 - var ipcClientService = new IpcClientService(IpcContext, peerName); + var ipcClientService = CreateIpcClientService(peerName); // 此时向对方注册,让对方重新触发逻辑 var shouldRegisterToPeer = true; var task = ipcClientService.Start(shouldRegisterToPeer: shouldRegisterToPeer); // 此时就建立完成了链接 - CreatePeerProxy(ipcClientService); + var peer = CreatePeerProxy(ipcClientService); // 先建立链接再继续发送注册,解决多进程同时注册 await task; + // 通知有其他客户端连接过来 + _ = IpcContext.TaskPool.Run(() => PeerConnected?.Invoke(this, new PeerConnectedArgs(peer)), IpcContext.Logger); + /* SendAckAndRegisterToPeer(); @@ -148,7 +162,7 @@ async void SendAckAndRegisterToPeer() } - void CreatePeerProxy(IpcClientService ipcClientService) + PeerProxy CreatePeerProxy(IpcClientService ipcClientService) { var peerProxy = new PeerProxy(e.PeerName, ipcClientService, e, IpcContext); @@ -162,8 +176,7 @@ void CreatePeerProxy(IpcClientService ipcClientService) Debug.Assert(false, "对方的连接并发进入,此时也许会存在多次重复连接对方的服务器端"); } - // 通知有其他客户端连接过来 - PeerConnected?.Invoke(this, new PeerConnectedArgs(peerProxy)); + return peerProxy; } } @@ -193,7 +206,7 @@ public async Task GetAndConnectToPeerAsync(string peerName) /// internal async Task GetOrCreatePeerProxyAsync(string peerName) { - if (PeerManager.ConnectedServerManagerList.TryGetValue(peerName, out var peerProxy)) + if (PeerManager.TryGetValue(peerName, out var peerProxy)) { } else @@ -201,22 +214,35 @@ internal async Task GetOrCreatePeerProxyAsync(string peerName) // 这里无视多次加入,这里的多线程问题也可以忽略 StartServer(); - var ipcClientService = new IpcClientService(IpcContext, peerName); + peerProxy = await CreatePeerProxyAsync(peerName); + } - peerProxy = new PeerProxy(peerName, ipcClientService, IpcContext); - PeerManager.TryAdd(peerProxy); + return peerProxy; + } - await ipcClientService.Start(); - } + private async Task CreatePeerProxyAsync(string peerName) + { + var ipcClientService = CreateIpcClientService(peerName); + + var peerProxy = new PeerProxy(peerName, ipcClientService, IpcContext); + PeerManager.TryAdd(peerProxy); + + await ipcClientService.Start().ConfigureAwait(false); return peerProxy; } + internal IpcClientService CreateIpcClientService(string peerName) => new IpcClientService(IpcContext, peerName); + /// public void Dispose() { + //Debugger.Launch(); + IpcContext.IsDisposing = true; + IpcContext.Logger.Trace($"[IpcProvider][Dispose] {IpcContext.PipeName}"); IpcServerService.Dispose(); PeerManager.Dispose(); + IpcContext.IsDisposed = true; } private IpcServerService? _ipcServerService; diff --git a/src/dotnetCampus.Ipc/Pipes/IpcRequestHandlerProvider.cs b/src/dotnetCampus.Ipc/Pipes/IpcRequestHandlerProvider.cs new file mode 100644 index 00000000..d5b246cc --- /dev/null +++ b/src/dotnetCampus.Ipc/Pipes/IpcRequestHandlerProvider.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies; +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Diagnostics; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Extensions; + +namespace dotnetCampus.Ipc.Pipes +{ + /// + /// 关联 的联系 + /// + class IpcRequestHandlerProvider + { + public IpcRequestHandlerProvider(IpcContext ipcContext) + { + IpcContext = ipcContext; + } + + public IpcContext IpcContext { get; } + + /// + /// 处理请求消息 + /// + /// + /// + /// 有三步 + /// 1. 取出消息和上下文里面带的 用于处理消息 + /// 2. 构建出 传入到 处理 + /// 3. 将 的返回值发送给到客户端 + public async void HandleRequest(PeerProxy sender, IpcClientRequestArgs args) + { + var requestMessage = args.IpcMessageBody; + var peerProxy = sender; + + IpcMessage ipcMessage = new IpcMessage($"[{peerProxy.PeerName}][{args.MessageId}]", requestMessage); + var ipcRequestContext = new IpcRequestMessageContext(ipcMessage, peerProxy, args.MessageCommandType.ToCoreMessageType()); + + // 处理消息 + // 优先从 Peer 里面找处理的方法,这样上层可以对某个特定的 Peer 做不同的处理 + // Todo 需要设计这部分 API 现在因为没有 API 的设计,先全部走 DefaultIpcRequestHandler 的逻辑 + var receiveRequestTracker = new IpcMessageTracker( + peerProxy.IpcContext.PipeName, + peerProxy.PeerName, + ipcRequestContext, + "HandleRequest", + IpcContext.Logger); + var result = await HandleRequestAsync(receiveRequestTracker, peerProxy.PeerName).ConfigureAwait(false); + + // 构建信息回复,发送回客户端。 + // 由于这里是通用的回复逻辑,所以只对需要回复的业务进行回复(不需要回复的业务就直接忽略)。 + var responseManager = IpcContext.IpcMessageResponseManager; + var responseMessage = responseManager.CreateResponseMessage(args.MessageId, result.ResponseMessage); + var sendResponseTracker = new IpcMessageTracker( + peerProxy.IpcContext.PipeName, + peerProxy.PeerName, + responseMessage, + "HandleRequest", + IpcContext.Logger); + sendResponseTracker.CriticalStep("Send", null, requestMessage); + await WriteResponseMessageAsync(peerProxy, sendResponseTracker).ConfigureAwait(false); + } + + private async Task HandleRequestAsync(IpcMessageTracker context, string remotePeerName) + { + IIpcResponseMessage? result = null; + + context.CriticalStep("ReceiveCore", null, context.Message.IpcBufferMessage.Body); + var handlers = IpcContext.IpcConfiguration.GetIpcRequestHandlers(); + foreach (var ipcRequestHandler in handlers) + { + result = await HandleRequestCoreAsync(context.Message, ipcRequestHandler).ConfigureAwait(false); + if (result is null) + { + var errorMessage = $"在实现 {nameof(IIpcRequestHandler)}.{nameof(IIpcRequestHandler.HandleRequest)} 时,必须返回非 null 的响应。如果不知道如何处理此消息,请返回 {nameof(KnownIpcResponseMessages)}.{nameof(KnownIpcResponseMessages.CannotHandle)}"; + IpcContext.Logger.Error(errorMessage); +#if DEBUG + throw new InvalidOperationException(errorMessage); +#endif + } + if (result != KnownIpcResponseMessages.CannotHandle) + { + break; + } + } + + if (result == null || result == KnownIpcResponseMessages.CannotHandle || result.ResponseMessage.Body.Length <= 0) + { + var possibeMessageContent = Encoding.UTF8.GetString(context.Message.IpcBufferMessage.Body.Buffer, context.Message.IpcBufferMessage.Body.Start, context.Message.IpcBufferMessage.Body.Length); + var errorMessage = $"IPC 端 {remotePeerName} 正在等待返回,因此必须至少有一个 IPC 处理器正常处理此消息返回。出现此异常代表代码编写出现了错误,必须修复。"; + var errorHandlers = string.Join("\r\n", handlers.Select(FormatHandlerAsErrorMessage)); + var logMessage = $"{errorMessage}\r\n消息内容猜测为:\r\n{possibeMessageContent}\r\n消息处理器有:\r\n{errorHandlers}\r\n说明所有这些消息处理器都没有处理此条消息,请添加更多的消息处理器。"; + IpcContext.Logger.Error(logMessage); +#if DEBUG + // 这里一定说明业务代码写错了,缺少对应的 Handler。 + throw new InvalidOperationException(logMessage); +#endif + } + + return result; + } + + private string FormatHandlerAsErrorMessage(IIpcRequestHandler handler) => handler switch + { + GeneratedProxyJointIpcRequestHandler gpjirh => string.Join(", ", gpjirh.Owner.JointManager.EnumerateJointNames()), + DelegateIpcRequestHandler dirh => nameof(DelegateIpcRequestHandler), + EmptyIpcRequestHandler eirh => nameof(EmptyIpcRequestHandler), + null => "null", + _ => handler.GetType().FullName!, + }; + + private static async Task HandleRequestCoreAsync(IpcRequestMessageContext message, IIpcRequestHandler ipcRequestHandler) + { + var result = await ipcRequestHandler.HandleRequest(message).ConfigureAwait(false); + return result; + } + + private async Task WriteResponseMessageAsync(PeerProxy peerProxy, IpcMessageTracker sendResponseTracker) + { + try + { + await peerProxy.IpcClientService.WriteMessageAsync(sendResponseTracker).ConfigureAwait(false); + } + catch (IOException) + { + // [正常现象] 在对方发完消息等待我方回复时退出。 + } + catch (ObjectDisposedException) + { + // Cannot access a closed pipe. + // [正常现象] 在对方发完消息等待我方回复时退出。 + } + catch (Exception ex) + { + IpcContext.Logger.Error(ex.ToString()); +#if DEBUG + // 这里一定说明业务代码写错了,缺少对应的 Handler。 + throw; +#endif + } + } + } +} diff --git a/src/dotnetCampus.Ipc/Pipes/IpcRequestMessageContext.cs b/src/dotnetCampus.Ipc/Pipes/IpcRequestMessageContext.cs new file mode 100644 index 00000000..5bada9fe --- /dev/null +++ b/src/dotnetCampus.Ipc/Pipes/IpcRequestMessageContext.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Pipes +{ + class IpcRequestMessageContext : ICoreIpcRequestContext, IIpcRequestContext + { + [DebuggerStepThrough] + internal IpcRequestMessageContext(IpcMessage ipcBufferMessage, IPeerProxy peer, CoreMessageType coreMessageType) + { + IpcBufferMessage = ipcBufferMessage; + Peer = peer; + CoreMessageType = coreMessageType; + } + + /// + public bool Handled { get; set; } + + /// + public IpcMessage IpcBufferMessage { get; } + + public IPeerProxy Peer { get; } + + public CoreMessageType CoreMessageType { get; } + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcServerService.cs b/src/dotnetCampus.Ipc/Pipes/IpcServerService.cs similarity index 76% rename from src/dotnetCampus.Ipc.PipeCore/Core_/IpcServerService.cs rename to src/dotnetCampus.Ipc/Pipes/IpcServerService.cs index d06e5e08..94147503 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Core_/IpcServerService.cs +++ b/src/dotnetCampus.Ipc/Pipes/IpcServerService.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.Utils; -namespace dotnetCampus.Ipc.PipeCore +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Utils.Extensions; +using dotnetCampus.Ipc.Utils.Logging; + +namespace dotnetCampus.Ipc.Pipes { /// /// 管道服务端,用于接收消息 @@ -45,7 +48,7 @@ public async Task Start() var pipeServerMessage = new IpcPipeServerMessageProvider(IpcContext, this); IpcPipeServerMessageProviderList.Add(pipeServerMessage); - await pipeServerMessage.Start(); + await pipeServerMessage.Start().ConfigureAwait(false); } } @@ -59,15 +62,19 @@ public async Task Start() /// internal event EventHandler? PeerConnected; - internal void OnMessageReceived(object? sender, PeerMessageArgs e) + internal void OnMessageReceived(object? sender, PeerStreamMessageArgs e) { - Logger.Debug($"[{nameof(IpcServerService)}] MessageReceived PeerName={e.PeerName} {e.Ack}"); - MessageReceived?.Invoke(sender, e); + string? debugMessageBody = null; +#if DEBUG + debugMessageBody = e.ToDebugMessageText(); +#endif + Logger.Trace($"[{nameof(IpcServerService)}] MessageReceived PeerName={e.PeerName} {e.Ack}{(debugMessageBody is null ? "" : $" {debugMessageBody}")}"); + MessageReceived?.Invoke(sender, e.ToPeerMessageArgs()); } internal void OnPeerConnected(object? sender, IpcInternalPeerConnectedArgs e) { - Logger.Debug($"[{nameof(IpcServerService)}] PeerConnected PeerName={e.PeerName} {e.Ack}"); + Logger.Trace($"[{nameof(IpcServerService)}] PeerConnected PeerName={e.PeerName} {e.Ack}"); PeerConnected?.Invoke(sender, e); } diff --git a/src/dotnetCampus.Ipc/Pipes/PeerProxy.cs b/src/dotnetCampus.Ipc/Pipes/PeerProxy.cs new file mode 100644 index 00000000..8fabfb28 --- /dev/null +++ b/src/dotnetCampus.Ipc/Pipes/PeerProxy.cs @@ -0,0 +1,289 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Diagnostics; +using dotnetCampus.Ipc.Exceptions; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; +using Newtonsoft.Json.Schema; + +namespace dotnetCampus.Ipc.Pipes +{ + /// + /// 用于表示远程的对方 + /// + public class PeerProxy : IPeerProxy + // 为什么 PeerProxy 不加上 IDisposable 方法 + // 因为这个类在上层业务使用,如果被上层业务调释放了,框架层就没得玩 + //, IDisposable + { + internal PeerProxy(string peerName, IpcClientService ipcClientService, IpcContext ipcContext) + { + PeerName = peerName; + IpcClientService = ipcClientService; + + IpcContext = ipcContext; + + IpcMessageRequestManager = new IpcMessageRequestManager(); + IpcMessageRequestManager.OnIpcClientRequestReceived += ResponseManager_OnIpcClientRequestReceived; + } + + internal PeerProxy(string peerName, IpcClientService ipcClientService, IpcInternalPeerConnectedArgs ipcInternalPeerConnectedArgs, IpcContext ipcContext) : + this(peerName, ipcClientService, ipcContext) + { + Update(ipcInternalPeerConnectedArgs); + } + + /// + /// 对方的服务器名 + /// + public string PeerName { get; } + + internal TaskCompletionSource WaitForFinishedTaskCompletionSource { private set; get; } = + new TaskCompletionSource(); + + internal IpcContext IpcContext { get; } + + /// + /// 当收到消息时触发 + /// + public event EventHandler? MessageReceived; + + internal IpcMessageRequestManager IpcMessageRequestManager { get; } + + /// + public async Task NotifyAsync(IpcMessage request) + { + // 追踪业务消息。 + var requestTracker = new IpcMessageTracker(IpcContext.PipeName, PeerName, request.Body, request.Tag, IpcContext.Logger); + requestTracker.CriticalStep("Send", null, request.Body); + + await WaitConnectAsync(requestTracker); + + // 发送带有追踪的请求。 + await IpcClientService.WriteMessageAsync(requestTracker).ConfigureAwait(false); + } + + /// + public async Task GetResponseAsync(IpcMessage request) + { + // 追踪业务消息。 + var requestTracker = new IpcMessageTracker(IpcContext.PipeName, PeerName, request, request.Tag, IpcContext.Logger); + requestTracker.CriticalStep("Send", null, request.Body); + await WaitConnectAsync(requestTracker); + + // 将业务消息封装成请求消息,并追踪。 + var ipcClientRequestMessage = IpcMessageRequestManager.CreateRequestMessage(request); + var ipcBufferMessageContextTracker = requestTracker.TrackNext(ipcClientRequestMessage.IpcBufferMessageContext); + + // 发送带有追踪的请求。 + await IpcClientService.WriteMessageAsync(ipcBufferMessageContextTracker).ConfigureAwait(false); + + // 等待响应,并追踪。 + var messageBody = await ipcClientRequestMessage.Task.ConfigureAwait(false); + return new IpcMessage($"[{PeerName}]", messageBody); + } + + /// + public event EventHandler? PeerConnectionBroken; + + /// + public event EventHandler? PeerReconnected; + + #region 框架 + + /// + /// 用于写入裸数据 + /// + /// 框架内使用 + internal IpcMessageWriter IpcMessageWriter { private set; get; } + // 由构造函数初始化 IpcClientService 时,自动初始化此属性,因此不为空 + = null!; + + /// + /// 表示作为客户端和对方连接 + /// + /// 框架内使用 + internal IpcClientService IpcClientService + { + private set + { + IpcMessageWriter = new IpcMessageWriter(value); + _ipcClientService = value; + } + get + { + return _ipcClientService; + } + } + + private IpcClientService _ipcClientService = null!; + + /// + /// 是否已断开 + /// + /// 特别和 分开两个属性,一个对外,一个给框架内使用 + /// 核心是用来实现重连的逻辑 + internal bool IsBroken { get; private set; } + + /// + /// 获取是否连接完成,也就是可以读取,可以发送 + /// + /// 连接过程中,断开,此时依然是 true 的值。在此时进行写入的内容,都会加入到缓存里面 + public bool IsConnectedFinished { get; private set; } + + /// + /// 用于记录数据,记录是否附加上重新连接 + /// + internal PeerReConnector? PeerReConnector { set; get; } + + private bool AutoReconnectPeers => _ipcClientService.IpcContext.IpcConfiguration.AutoReconnectPeers; + + /// + /// 被对方连回来时调用,此方法被调用意味着准备已完成 + /// + /// + internal void Update(IpcInternalPeerConnectedArgs ipcInternalPeerConnectedArgs) + { + Debug.Assert(ipcInternalPeerConnectedArgs.PeerName == PeerName); + + var serverStreamMessageReader = ipcInternalPeerConnectedArgs.ServerStreamMessageReader; + + serverStreamMessageReader.MessageReceived -= ServerStreamMessageReader_MessageReceived; + serverStreamMessageReader.MessageReceived += ServerStreamMessageReader_MessageReceived; + + // 连接断开 + serverStreamMessageReader.PeerConnectBroke -= ServerStreamMessageReader_PeerConnectBroke; + serverStreamMessageReader.PeerConnectBroke += ServerStreamMessageReader_PeerConnectBroke; + + IsConnectedFinished = true; + IsBroken = false; + + if (WaitForFinishedTaskCompletionSource.TrySetResult(true)) + { + } + else + { + // Debug.Assert(false, "重复调用"); + } + } + + /// + /// 重新连接 + /// + internal void Reconnect(IpcClientService ipcClientService) + { + Debug.Assert(ipcClientService.PeerName == PeerName); + + IpcClientService = ipcClientService; + + PeerReconnected?.Invoke(this, new PeerReconnectedArgs()); + } + + private void ServerStreamMessageReader_PeerConnectBroke(object? sender, PeerConnectionBrokenArgs e) + { + OnPeerConnectionBroken(e); + } + + private void ServerStreamMessageReader_MessageReceived(object? sender, PeerStreamMessageArgs e) + { + IpcMessageRequestManager.OnReceiveMessage(e); + var args = e.ToPeerMessageArgs(); + + var messageTracker = new IpcMessageTracker( + IpcContext.PipeName, + PeerName, + args.Message, + "MessageReceived", + IpcContext.Logger); + messageTracker.CriticalStep("Receive", e.Ack, messageTracker.Message.Body); + IpcContext.TaskPool.Run(() => MessageReceived?.Invoke(this, args), IpcContext.Logger); + } + + private void ResponseManager_OnIpcClientRequestReceived(object? sender, IpcClientRequestArgs e) + { + var ipcRequestHandlerProvider = IpcContext.IpcRequestHandlerProvider; + ipcRequestHandlerProvider.HandleRequest(this, e); + } + + private void OnPeerConnectionBroken(IPeerConnectionBrokenArgs e) + { + IsBroken = true; + + if (AutoReconnectPeers) + { + WaitForFinishedTaskCompletionSource = new TaskCompletionSource(); + } + + IpcClientService.Dispose(); + + PeerConnectionBroken?.Invoke(this, e); + + IpcMessageRequestManager.BreakAllRequestTaskByIpcBroken(); + } + + private async Task WaitConnectAsync(IIpcMessageTracker requestTracker) + { + if (IsBroken) + { + if (IpcContext.IsDisposing || IpcContext.IsDisposed) + { + throw new ObjectDisposedException(nameof(PeerProxy), + $"当前服务已被释放,服务名: LocalPeerName={IpcContext.PipeName}; MessageTag={requestTracker.Tag}"); + } + + if (AutoReconnectPeers) + { +#if DEBUG + requestTracker.Debug("[Reconnect] Waiting FinishedTaskCompletion"); +#endif + + // 如果完全,且断开,且需要自动连接 + // 这…… 下面实现了简单的自旋,理论上是无伤的 + while (WaitForFinishedTaskCompletionSource.Task.IsCompleted && IsBroken) + { + // 如果只执行到设置 IsBroken=true 还没有创建新 WaitForFinishedTaskCompletionSource 对象 + // 那么此时 IsCompleted=true 且 IsBroken=true 满足 + // 进入等待 + + // 如果特别快,进入 IsBroken=true 之后立刻创建 WaitForFinishedTaskCompletionSource 对象 + // 自然此时的 IsCompleted=false 进入下面的等待 + + // 如果特别特别快,断开后立刻连接 + // 那么 IsCompleted=true 满足,但是 IsBroken=false 了 + // 进入下面的等待 + await Task.Yield(); + } + await WaitForFinishedTaskCompletionSource.Task; +#if DEBUG + requestTracker.Debug("[Reconnect] Finish Waiting FinishedTaskCompletion"); +#endif + } + else + { + throw new IpcPeerConnectionBrokenException(); + } + } + } + + #endregion + + /// + /// 释放当前的对象,必须由框架内调用 + /// + internal void DisposePeer() + { + if (!IpcContext.IsDisposing) + { + throw new InvalidOperationException($"仅允许在 IpcProvider 的 Dispose 进行调用"); + } + + IsBroken = true; + // 不管此状态 + //IsConnectedFinished = true; + _ipcClientService.Dispose(); + } + } +} diff --git a/src/dotnetCampus.Ipc.Abstractions/IpcObjectSerializer_/IIpcObjectSerializer.cs b/src/dotnetCampus.Ipc/Serialization/IIpcObjectSerializer.cs similarity index 64% rename from src/dotnetCampus.Ipc.Abstractions/IpcObjectSerializer_/IIpcObjectSerializer.cs rename to src/dotnetCampus.Ipc/Serialization/IIpcObjectSerializer.cs index 53cb5e72..81e33d0f 100644 --- a/src/dotnetCampus.Ipc.Abstractions/IpcObjectSerializer_/IIpcObjectSerializer.cs +++ b/src/dotnetCampus.Ipc/Serialization/IIpcObjectSerializer.cs @@ -1,4 +1,4 @@ -namespace dotnetCampus.Ipc.Abstractions +namespace dotnetCampus.Ipc.Serialization { /// /// 对象序列化器 @@ -8,16 +8,16 @@ public interface IIpcObjectSerializer /// /// 序列化对象 /// - /// + /// /// - byte[] Serialize(object obj); + byte[] Serialize(object value); /// /// 反序列化对象 /// /// - /// + /// /// - T Deserialize(byte[] byteList); + T Deserialize(byte[] data); } } diff --git a/src/dotnetCampus.Ipc/Utils/IpcObjectSerializer_/IpcObjectJsonSerializer.cs b/src/dotnetCampus.Ipc/Serialization/IpcObjectJsonSerializer.cs similarity index 69% rename from src/dotnetCampus.Ipc/Utils/IpcObjectSerializer_/IpcObjectJsonSerializer.cs rename to src/dotnetCampus.Ipc/Serialization/IpcObjectJsonSerializer.cs index e888ab9d..bc3cd97a 100644 --- a/src/dotnetCampus.Ipc/Utils/IpcObjectSerializer_/IpcObjectJsonSerializer.cs +++ b/src/dotnetCampus.Ipc/Serialization/IpcObjectJsonSerializer.cs @@ -1,14 +1,14 @@ using System.Text; -using dotnetCampus.Ipc.Abstractions; + using Newtonsoft.Json; -namespace dotnetCampus.Ipc.Utils +namespace dotnetCampus.Ipc.Serialization { public class IpcObjectJsonSerializer : IIpcObjectSerializer { - public byte[] Serialize(object obj) + public byte[] Serialize(object value) { - var json = JsonConvert.SerializeObject(obj); + var json = JsonConvert.SerializeObject(value); return Encoding.UTF8.GetBytes(json); } diff --git a/src/dotnetCampus.Ipc/Serialization/JsonIpcMessageSerializer.cs b/src/dotnetCampus.Ipc/Serialization/JsonIpcMessageSerializer.cs new file mode 100644 index 00000000..135bab01 --- /dev/null +++ b/src/dotnetCampus.Ipc/Serialization/JsonIpcMessageSerializer.cs @@ -0,0 +1,83 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +using dotnetCampus.Ipc.Messages; + +using Newtonsoft.Json; + +namespace dotnetCampus.Ipc.Serialization +{ + /// + /// 如果某 IPC 消息计划以 JSON 格式传输,那么可使用此类型来序列化和反序列化。 + /// + public static class JsonIpcMessageSerializer + { + /// + /// 将 IPC 模块自动生成的内部模型序列化成可供跨进程传输的 IPC 消息。 + /// + /// + /// + /// + public static IpcMessage Serialize(string tag, object model) + { + if (model is null) + { + throw new ArgumentNullException(nameof(model)); + } + + var json = JsonConvert.SerializeObject(model); + var data = Encoding.UTF8.GetBytes(json); + var message = new IpcMessage(tag, new IpcMessageBody(data)); + return message; + } + + /// + /// 尝试将跨进程传输过来的 IPC 消息反序列化成 IPC 模块自动生成的内部模型。 + /// + /// IPC 消息。 + /// + /// + public static bool TryDeserialize(IpcMessage message, [NotNullWhen(true)] out T? model) where T : class, new() + { + var body = message.Body; + var stringBody = Encoding.UTF8.GetString(body.Buffer, body.Start, body.Length); + if (string.IsNullOrWhiteSpace(stringBody)) + { + model = new T(); + return true; + } + try + { + model = JsonConvert.DeserializeObject(stringBody); + return true; + } + catch (JsonSerializationException) + { + model = null; + return false; + } + catch (JsonReaderException) + { + // Newtonsoft.Json.JsonReaderException + // Unexpected character encountered while parsing value: {0}. + // JSON 字符串中包含不符合格式的字符。典型情况如下: + // * IPC 消息头被意外合入了消息体 + // * 待发现…… + model = null; + return false; + } + catch + { + // 此反序列化过程抛出异常是合理行为(毕竟 IPC 谁都能发,但应该主要是 JsonSerializationException)。 + // 目前来看,还不知道有没有一些合理的正常的情况下会抛出其他异常,因此暂时在 !DEBUG 下不处理消息。 +#if DEBUG + throw; +#else + model = null; + return false; +#endif + } + } + } +} diff --git a/src/dotnetCampus.Ipc/Threading/IpcThreadPool.cs b/src/dotnetCampus.Ipc/Threading/IpcThreadPool.cs new file mode 100644 index 00000000..cadfdaf2 --- /dev/null +++ b/src/dotnetCampus.Ipc/Threading/IpcThreadPool.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Utils.Extensions; +using dotnetCampus.Ipc.Utils.Logging; + +using Producer = System.Collections.Concurrent.BlockingCollection; + +namespace dotnetCampus.Ipc.Threading +{ + /// + /// 提供给 IPC 框架内部使用的线程池。 + /// 专为调用时长不确定的业务代码而设计。 + /// 此类型的所有公共方法都不是线程安全的,因此你需要确保在临界区调用这些代码。 + /// 但内部方法是线程安全的。 + /// + internal sealed class IpcThreadPool + { + /// + /// 为每一个创建的线程名称准备一个序号。 + /// + private volatile int _threadIndex; + + /// + /// 目前正在执行 IPC 任务的线程总数(含正在执行任务和和正在等待执行任务的)。 + /// + private volatile int _threadCount; + + /// + /// 目前正在等待分配任务的线程数。 + /// + private volatile int _availableCount; + + /// + /// 最近一次回收线程的时间,如果短时间内大量触发则不回收。 + /// + private DateTime _lastRecycleTime; + + /// + /// 防止回收重入。 + /// + private volatile int _isRecycling; + + /// + /// 当前正在等待延时启动线程的任务。 + /// + private readonly ConcurrentQueue _delayStartWaitings = new(); + + /// + /// 任务队列,与 配合使用。 + /// 这里放真实的任务。 + /// + /// 为什么不放到 里呢? + /// 是因为那按时间顺序放的任务,到那里会等线程调度,真正执行的时候可能顺序就乱了。 + /// 于是到真正调度到的时候再取任务,可以更大概率避免调度时间差导致的顺序问题。 + /// + /// + private readonly ConcurrentQueue _taskQueue = new(); + + /// + /// 线程队列,与 配合使用。 + /// 这里放此刻可供调度的线程资源。 + /// + /// 存一个线程和其对应的调度器,不放具体的任务。 + /// 不放具体任务是因为按时间顺序调度的任务,真正执行的时候可能顺序会乱了。 + /// + /// + private readonly ConcurrentQueue<(Thread thread, Producer producer)> _workingThreads = new(); + + /// + /// 在线程池挑选一个线程执行指定代码。 + /// 当任务确定已经开始执行之后就会返回第一层 , + /// 在任务和超时时间先完成者会再次返回第二层 。 + /// 注意,当线程池中线程数量不足时,第一层 的返回会延迟,直到空出新的可供执行的线程资源。 + /// + /// + /// + /// + /// + /// 特别注意!!! + /// 此方法不是线程安全的,调用方必须确保此方法的调用处于临界区。 + /// + public Task Run(Action action, ILogger? logger) + { + var taskItem = new IpcStartEndTaskItem(action); + RunRecursively(taskItem, logger); + return taskItem.AsTask(); + } + + private async void RunRecursively(IpcStartEndTaskItem taskItem, ILogger? logger) + { + // 如果打算开始一个任务,则从线程池中取一个线程。 + if (_workingThreads.TryDequeue(out var result)) + { + // 线程池中有空闲线程。 + var producer = result.producer; + _taskQueue.Enqueue(taskItem); + producer.Add(0); + } + else + { + // 线程池中所有线程繁忙。 + var count = _threadCount; + var delayTime = GetCurrentStartThreadDelayTime(count); + if (delayTime == TimeSpan.Zero) + { + // 线程池中虽没有空闲线程,但打算立即启动新线程。 + var producer = new Producer(); + StartThread(producer, logger); + _taskQueue.Enqueue(taskItem); + producer.Add(0); + } + else + { + // 线程池中没有空闲线程,因此等待一段时间后重试。 + Log(logger, $"因为线程资源紧张({count} 个),延迟 {delayTime.TotalMilliseconds}ms 后重新调度。"); + await DelayOrAnyThreadAvailable(delayTime); + // 等待结束后重跑线程调度。 + RunRecursively(taskItem, logger); + } + } + } + + private void StartThread(Producer c, ILogger? logger) + { + var index = Interlocked.Increment(ref _threadIndex); + var t = new Thread(OnThreadStart) + { + Name = $"IPC-{index.ToString(CultureInfo.InvariantCulture).PadLeft(2, '0')}", + IsBackground = true, + }; + var count = Interlocked.Increment(ref _threadCount); + t.Start((c, logger)); + } + + private void OnThreadStart(object? arg) + { + var thread = Thread.CurrentThread; + var (producer, logger) = ((Producer, ILogger?)) arg!; + Interlocked.Increment(ref _availableCount); + Log(logger, $"线程启动 {thread.Name}"); + foreach (var _ in producer.GetConsumingEnumerable()) + { + if (!_taskQueue.TryDequeue(out var taskItem)) + { + throw new InvalidOperationException("100% BUG。先加入的任务,后面必定可以取到。"); + } + var action = taskItem.Action; + try + { + // 开始执行任务。 + Interlocked.Decrement(ref _availableCount); + taskItem.Start(); + action(); + } + catch (Exception ex) + { + throw new InvalidOperationException("调用 IpcThreadPool.Run 方法时传入的动作必须完全自行处理好异常,不允许让任何异常泄漏出来。", ex); + } + finally + { + // 完成执行任务。 + taskItem.End(); + // 回收线程(先回收线程,以防把本线程回收掉,导致任何任务都新开启线程执行)。 + RecycleUselessThreads(logger); + // 重新回到线程池。 + _workingThreads.Enqueue((thread, producer)); + // 可用线程数 +1。 + Interlocked.Increment(ref _availableCount); + // 如果有等待线程启动,则立即结束等待。 + ClearWaitings(); + } + } + producer.Dispose(); + Log(logger, $"线程退出 {thread.Name}"); + } + + private void ClearWaitings() + { + while (_delayStartWaitings.TryDequeue(out var tokenSource)) + { + tokenSource.Cancel(); + } + } + + /// + /// 等待一个制定的时间。但如果有任何一个线程空闲,则等待立即完成。 + /// + /// + /// + private async Task DelayOrAnyThreadAvailable(TimeSpan delayTime) + { + var taskSource = new CancellationTokenSource(); + _delayStartWaitings.Enqueue(taskSource); + try + { + while (taskSource.IsCancellationRequested && delayTime > TimeSpan.Zero) + { + await Task.Delay(15, taskSource.Token); + delayTime -= TimeSpan.FromMilliseconds(15); + } + } + catch (OperationCanceledException) + { + // 等待已结束。 + } + } + + private TimeSpan GetCurrentStartThreadDelayTime(int count) + { + if (_availableCount > 0) + { + // 如果当前存在可供使用的线程资源,则直接开启线程。 + return TimeSpan.Zero; + } + + var delay = ThreadCountToDelayTime(count); + return delay; + } + + /// + /// 根据当前已有的线程总数来决定新启动下一个线程的等待时间。 + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private TimeSpan ThreadCountToDelayTime(int count) => count switch + { + 0 => TimeSpan.Zero, + 1 => TimeSpan.Zero, + 2 => TimeSpan.Zero, + 3 => TimeSpan.Zero, + 4 => TimeSpan.Zero, + 5 => TimeSpan.FromMilliseconds(250), + 6 => TimeSpan.FromMilliseconds(500), + 7 => TimeSpan.FromSeconds(1), + 8 => TimeSpan.FromSeconds(2), + 9 => TimeSpan.FromSeconds(4), + 10 => TimeSpan.FromSeconds(10), + _ => TimeSpan.FromSeconds(30), + }; + + /// + /// 回收线程 + /// + /// + private void RecycleUselessThreads(ILogger? logger) + { + var now = DateTime.Now; + var elapsed = now - _lastRecycleTime; + // 1 秒内最多回收一次线程。 + if (elapsed > TimeSpan.FromSeconds(1)) + { + var isRecycling = Interlocked.CompareExchange(ref _isRecycling, 1, 0); + if (isRecycling is 1) + { + return; + } + + _lastRecycleTime = now; + // 期望回收数为当前空闲线程数的一半。 + + try + { + RecycleCore(); + } + finally + { + _isRecycling = 0; + } + } + + void RecycleCore() + { + var desiredDisposeCount = _workingThreads.Count / 2; + while (desiredDisposeCount > 0) + { + desiredDisposeCount--; + if (_workingThreads.TryDequeue(out var result)) + { + var (thread, producer) = result; + Log(logger, $"回收线程 {thread.Name}"); + producer.CompleteAdding(); + } + } + } + } + + private void Log(ILogger? logger, string message) + { + logger.Information($"[{nameof(IpcThreadPool)}] {message}"); + } + } +} diff --git a/src/dotnetCampus.Ipc/Threading/Tasks/IpcStartEndTaskItem.cs b/src/dotnetCampus.Ipc/Threading/Tasks/IpcStartEndTaskItem.cs new file mode 100644 index 00000000..7d4dc5c3 --- /dev/null +++ b/src/dotnetCampus.Ipc/Threading/Tasks/IpcStartEndTaskItem.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; + +namespace dotnetCampus.Ipc.Threading +{ + internal sealed class IpcStartEndTaskItem + { + private readonly TaskCompletionSource> _actionStartedTaskSource = new(); + private readonly TaskCompletionSource _actionEndedTaskSource = new(); + + public IpcStartEndTaskItem(Action action) + { + Action = action; + } + + public Action Action { get; } + + public async Task AsTask() + { + var endTask = await _actionStartedTaskSource.Task.ConfigureAwait(false); + return endTask.Task; + } + + internal void Start() + { + _actionStartedTaskSource.SetResult(_actionEndedTaskSource); + } + + internal void End() + { + _actionEndedTaskSource.SetResult(true); + } + } +} diff --git a/src/dotnetCampus.Ipc/Threading/Tasks/IpcTask.cs b/src/dotnetCampus.Ipc/Threading/Tasks/IpcTask.cs new file mode 100644 index 00000000..51e6883b --- /dev/null +++ b/src/dotnetCampus.Ipc/Threading/Tasks/IpcTask.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Utils.Logging; + +namespace dotnetCampus.Ipc.Threading.Tasks +{ + /// + /// 管理一组 IPC 任务。 + /// 加入此 的任务严格按照先到先执行的原则依次执行; + /// 但与普通队列不同的是,一旦任务开始执行后,便可根据配置决定是否并发执行后续任务(而不必等待任务完全执行完成); + /// 并且支持限制并发数量(以避免潜在的性能影响)。 + /// + internal sealed class IpcTask + { + private volatile int _isRunning; + private readonly ConcurrentQueue _queue = new(); + private readonly IpcThreadPool _threadPool = new(); + + /// + /// 由于原子操作仅提供高性能的并发处理而不保证准确性,因此需要一个锁来同步 中值为 0 时所指的不确定情况。 + /// 不能使用一个锁来同步所有情况是因为在锁中使用 async/await 是不安全的,因此避免在锁中执行异步任务;我们使用原子操作来判断异步任务的执行条件。 + /// + private readonly object _locker = new(); + + /// + /// 支持并发进入的 IPC 任务。 + /// 被此 管理的异步任务将按调用此方法的顺序依次开始执行,至于开始后多少个任务可以同时运行或者执行多长时间后算超时而执行下一个取决于此 的配置。 + /// + /// + /// + /// + /// + public Task Run(Action action, ILogger? logger = null) => Run(() => + { + action(); + return Task.FromResult(0); + }, logger); + + /// + /// 支持并发进入的 IPC 任务。 + /// 被此 管理的异步任务将按调用此方法的顺序依次开始执行,至于开始后多少个任务可以同时运行或者执行多长时间后算超时而执行下一个取决于此 的配置。 + /// + /// + /// + /// + /// + public Task Run(Func> task, ILogger? logger = null) + { + var item = new TaskItem(task, logger); + _queue.Enqueue(item); + ResumeRunning(); + return item.AsTask(); + } + + private async void ResumeRunning() + { + var isRunning = Interlocked.CompareExchange(ref _isRunning, 1, 0); + if (isRunning is 1) + { + lock (_locker) + { + if (_isRunning is 1) + { + // 当前已经在执行队列,因此无需继续执行。 + return; + } + } + } + + var hasTask = true; + while (hasTask) + { + // 当前还没有任何队列开始执行,因此需要开始执行队列。 + while (_queue.TryDequeue(out var taskItem)) + { + // 内部已包含异常处理,因此外面可以无需捕获或者清理。 + await ConsumeTaskItemAsync(taskItem).ConfigureAwait(false); + } + + lock (_locker) + { + hasTask = _queue.TryPeek(out _); + if (!hasTask) + { + _isRunning = 0; + } + } + } + } + + /// + /// 运行在其中一个调用线程中的临界区。 + /// 请勿执行耗时操作。 + /// + /// + private async Task ConsumeTaskItemAsync(TaskItem taskItem) + { + // 第一层等待,非常必要,这可以确保任务一定按顺序开始执行。 + var timeout = await _threadPool.Run(() => taskItem.Run(), taskItem.Logger).ConfigureAwait(false); + // timeout 是第二层等待,即任务完全执行完成(而这里我们并不关心,所以无视)。 + } + } +} diff --git a/src/dotnetCampus.Ipc/Threading/Tasks/TaskItem.cs b/src/dotnetCampus.Ipc/Threading/Tasks/TaskItem.cs new file mode 100644 index 00000000..f279757f --- /dev/null +++ b/src/dotnetCampus.Ipc/Threading/Tasks/TaskItem.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Utils.Logging; + +namespace dotnetCampus.Ipc.Threading.Tasks +{ + internal abstract class TaskItem + { + protected TaskItem(ILogger? logger) + { + Logger = logger; + } + + public ILogger? Logger { get; } + + internal abstract void Run(); + } + + internal sealed class TaskItem : TaskItem + { + private readonly TaskCompletionSource _source; + + public TaskItem(Func> func, ILogger? logger) + : base(logger) + { + _source = new TaskCompletionSource(); + Func = func; + } + + public Func> Func { get; } + + public Task AsTask() => _source.Task; + + internal override async void Run() + { + try + { + var value = await Func().ConfigureAwait(false); + _source.SetResult(value); + } + catch (Exception ex) + { + _source.SetException(ex); + } + } + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/Memory_/ISharedArrayPool.cs b/src/dotnetCampus.Ipc/Utils/Buffers/ISharedArrayPool.cs similarity index 93% rename from src/dotnetCampus.Ipc.PipeCore/Utils/Memory_/ISharedArrayPool.cs rename to src/dotnetCampus.Ipc/Utils/Buffers/ISharedArrayPool.cs index eb3a16e0..4faf5ed0 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/Memory_/ISharedArrayPool.cs +++ b/src/dotnetCampus.Ipc/Utils/Buffers/ISharedArrayPool.cs @@ -1,4 +1,4 @@ -namespace dotnetCampus.Ipc.PipeCore.Utils +namespace dotnetCampus.Ipc.Utils.Buffers { /// /// 提供共享的数组 diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/Memory_/SharedArrayPool.cs b/src/dotnetCampus.Ipc/Utils/Buffers/SharedArrayPool.cs similarity index 64% rename from src/dotnetCampus.Ipc.PipeCore/Utils/Memory_/SharedArrayPool.cs rename to src/dotnetCampus.Ipc/Utils/Buffers/SharedArrayPool.cs index ac04c90c..578f5448 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/Memory_/SharedArrayPool.cs +++ b/src/dotnetCampus.Ipc/Utils/Buffers/SharedArrayPool.cs @@ -1,4 +1,6 @@ -namespace dotnetCampus.Ipc.PipeCore.Utils +using System.Threading; + +namespace dotnetCampus.Ipc.Utils.Buffers { #if NETCOREAPP using System.Buffers; @@ -35,21 +37,36 @@ public void Return(byte[] array) } #else /// - /// 共享数组内存,没有真的实现 + /// 共享数组内存 /// + /// 低配版 public class SharedArrayPool : ISharedArrayPool { /// public byte[] Rent(int minLength) { + var cacheByteList = _cacheByteList.Value; + + if (cacheByteList != null && cacheByteList.Length >= minLength) + { + _cacheByteList.Value = null; + return cacheByteList; + } + return new byte[minLength]; } /// public void Return(byte[] array) { + var cacheByteList = _cacheByteList.Value; + if (cacheByteList == null || cacheByteList.Length < array.Length) + { + _cacheByteList.Value = cacheByteList; + } } + + private readonly ThreadLocal _cacheByteList = new ThreadLocal(); } #endif - } diff --git a/src/dotnetCampus.Ipc/Utils/ExceptionHacker.cs b/src/dotnetCampus.Ipc/Utils/ExceptionHacker.cs new file mode 100644 index 00000000..141c166c --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/ExceptionHacker.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics; +using System.Linq.Expressions; +using System.Reflection; + +namespace dotnetCampus.Ipc.Utils +{ + internal static class ExceptionHacker + { + internal static Exception ReplaceStackTrace(Exception target, string stack) => HackExceptionStackTrace(target, stack); + + private static readonly Func HackExceptionStackTrace = new Func>(() => + { + var target = Expression.Parameter(typeof(Exception)); + var stack = Expression.Parameter(typeof(string)); + var traceFormatType = typeof(StackTrace).GetNestedType("TraceFormat", BindingFlags.NonPublic)!; + var toString = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { traceFormatType }, null)!; + var normalTraceFormat = Enum.GetValues(traceFormatType).GetValue(0)!; + var stackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance)!; + var assign = Expression.Assign(Expression.Field(target, stackTraceStringField), stack); + return Expression.Lambda>(Expression.Block(assign, target), target, stack).Compile(); + })(); + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/BinaryArrayExtension.cs b/src/dotnetCampus.Ipc/Utils/Extensions/BinaryArrayExtensions.cs similarity index 58% rename from src/dotnetCampus.Ipc.PipeCore/Utils/BinaryArrayExtension.cs rename to src/dotnetCampus.Ipc/Utils/Extensions/BinaryArrayExtensions.cs index 129916e1..9bb2c1ca 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/BinaryArrayExtension.cs +++ b/src/dotnetCampus.Ipc/Utils/Extensions/BinaryArrayExtensions.cs @@ -1,15 +1,15 @@ using System; using System.Linq; -namespace dotnetCampus.Ipc.PipeCore.Utils +namespace dotnetCampus.Ipc.Utils.Extensions { - class BinaryArrayExtension + class BinaryArrayExtensions { public static byte[] Combine(params byte[][] arrays) { - byte[] ret = new byte[arrays.Sum(x => x.Length)]; - int offset = 0; - foreach (byte[] data in arrays) + var ret = new byte[arrays.Sum(x => x.Length)]; + var offset = 0; + foreach (var data in arrays) { Buffer.BlockCopy(data, 0, ret, offset, data.Length); offset += data.Length; diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/ByteListExtension.cs b/src/dotnetCampus.Ipc/Utils/Extensions/ByteListExtensions.cs similarity index 84% rename from src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/ByteListExtension.cs rename to src/dotnetCampus.Ipc/Utils/Extensions/ByteListExtensions.cs index 64561b33..0dd46118 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/ByteListExtension.cs +++ b/src/dotnetCampus.Ipc/Utils/Extensions/ByteListExtensions.cs @@ -1,6 +1,6 @@ -namespace dotnetCampus.Ipc.PipeCore.Utils.Extensions +namespace dotnetCampus.Ipc.Utils.Extensions { - internal static class ByteListExtension + internal static class ByteListExtensions { public static bool Equals(byte[] a, byte[] b, int length) { diff --git a/src/dotnetCampus.Ipc/Utils/Extensions/DoubleBufferTaskExtensions.cs b/src/dotnetCampus.Ipc/Utils/Extensions/DoubleBufferTaskExtensions.cs new file mode 100644 index 00000000..2df71a0d --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/Extensions/DoubleBufferTaskExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using dotnetCampus.Threading; + +namespace dotnetCampus.Ipc.Utils.Extensions +{ + static class DoubleBufferTaskExtensions + { + private static volatile int _count = 0; + + public static async Task AddTaskAsync(this DoubleBufferTask> doubleBufferTask, Func task) + { + var count = Interlocked.Increment(ref _count); + + LoggerExtensions.Trace(null, $"[{count}] [AddTaskAsync]"); + var taskCompletionSource = new TaskCompletionSource(); + + doubleBufferTask.AddTask(async () => + { + LoggerExtensions.Trace(null, $"[{count}] [doubleBufferTask.AddTask]"); + try + { + await task().ConfigureAwait(false); + LoggerExtensions.Trace(null, $"[{count}] [doubleBufferTask.AddTask] Completed"); + taskCompletionSource.SetResult(true); + } + catch (Exception e) + { + LoggerExtensions.Trace(null, $"[{count}] [doubleBufferTask.AddTask] Exception"); + taskCompletionSource.SetException(e); + } + }); + + await taskCompletionSource.Task.ConfigureAwait(false); + LoggerExtensions.Trace(null, $"[{count}] [taskCompletionSource.Task] Completed"); + } + } +} diff --git a/src/dotnetCampus.Ipc/Utils/Extensions/IpcBufferMessageExtensions.cs b/src/dotnetCampus.Ipc/Utils/Extensions/IpcBufferMessageExtensions.cs new file mode 100644 index 00000000..5c3e3729 --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/Extensions/IpcBufferMessageExtensions.cs @@ -0,0 +1,17 @@ +using System; + +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Extensions; + +namespace dotnetCampus.Ipc.Utils.Extensions +{ + static class IpcBufferMessageExtensions + { +#if NETCOREAPP3_1_OR_GREATER + public static Span AsSpan(this in IpcMessageBody message) + { + return message.Buffer.AsSpan(message.Start, message.Length); + } +#endif + } +} diff --git a/src/dotnetCampus.Ipc/Utils/Extensions/IpcMessageCommandExtensions.cs b/src/dotnetCampus.Ipc/Utils/Extensions/IpcMessageCommandExtensions.cs new file mode 100644 index 00000000..64e473fa --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/Extensions/IpcMessageCommandExtensions.cs @@ -0,0 +1,66 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Text; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Messages; + +namespace dotnetCampus.Ipc.Utils.Extensions +{ + /// + /// 对 提供扩展方法。 + /// + internal static class IpcMessageCommandExtensions + { + /// + /// 将 转换为可与 进行位运算的标识位枚举。 + /// + /// 要转换的 。 + /// 转换后的 + public static IpcMessageCommandType AsMessageCommandTypeFlags(this CoreMessageType coreMessageType) + { + return (IpcMessageCommandType) ((int) coreMessageType << 3); + } + + /// + /// 将 进行位运算以得出 消息类型。 + /// + /// 要转换的 。 + /// 转换后的 + public static CoreMessageType ToCoreMessageType(this IpcMessageCommandType ipcMessageCommandType) + { + return (0b_0000_0000_0011_1000 & (short) ipcMessageCommandType) switch + { + 0b_0000_0000_0000_1000 => CoreMessageType.Raw, + 0b_0000_0000_0001_0000 => CoreMessageType.String, + 0b_0000_0000_0010_0000 => CoreMessageType.JsonObject, + _ => CoreMessageType.NotMessageBody, + }; + } + +#if DEBUG + internal static string? ToDebugMessageText(this IpcMessageBody body, IpcMessageCommandType ipcMessageCommandType) + { + return ipcMessageCommandType.ToCoreMessageType() switch + { + CoreMessageType.JsonObject => Encoding.UTF8.GetString(body.Buffer, body.Start, body.Length), + CoreMessageType.String => Encoding.UTF8.GetString(body.Buffer, body.Start, body.Length), + _ => Encoding.UTF8.GetString(body.Buffer, body.Start, body.Length), + }; + } + + internal static string? ToDebugMessageText(this IpcBufferMessageContext context) + { + string? debugMessageBody = string.Join(Environment.NewLine, context.IpcBufferMessageList.Select(x => + x.ToDebugMessageText(context.IpcMessageCommandType) ?? "")); + return string.IsNullOrEmpty(debugMessageBody) ? null : debugMessageBody; + } + + internal static string? ToDebugMessageText(this PeerStreamMessageArgs args) + { + return args.ToPeerMessageArgs().Message.Body.ToDebugMessageText(args.MessageCommandType); + } +#endif + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/IpcMessageContextExtension.cs b/src/dotnetCampus.Ipc/Utils/Extensions/IpcMessageContextExtensions.cs similarity index 55% rename from src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/IpcMessageContextExtension.cs rename to src/dotnetCampus.Ipc/Utils/Extensions/IpcMessageContextExtensions.cs index eafe2670..1444a1ae 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/Extensions/IpcMessageContextExtension.cs +++ b/src/dotnetCampus.Ipc/Utils/Extensions/IpcMessageContextExtensions.cs @@ -1,8 +1,9 @@ -using dotnetCampus.Ipc.PipeCore.Context; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.IO; -namespace dotnetCampus.Ipc.PipeCore.Utils.Extensions +namespace dotnetCampus.Ipc.Utils.Extensions { - static class IpcMessageContextExtension + static class IpcMessageContextExtensions { public static ByteListMessageStream ToStream(this in IpcMessageContext ipcMessageContext) { diff --git a/src/dotnetCampus.Ipc/Utils/Extensions/LoggerExtensions.cs b/src/dotnetCampus.Ipc/Utils/Extensions/LoggerExtensions.cs new file mode 100644 index 00000000..bb881d7c --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/Extensions/LoggerExtensions.cs @@ -0,0 +1,53 @@ +using System; + +using dotnetCampus.Ipc.Utils.Logging; + +namespace dotnetCampus.Ipc.Utils.Extensions +{ + internal static class LoggerExtensions + { + public static void Trace(this ILogger? logger, string message) + { + logger?.Log(LogLevel.Trace, default, 0, null, (s, e) => message); + } + + public static void Debug(this ILogger? logger, string message) + { + logger?.Log(LogLevel.Debug, default, 0, null, (s, e) => message); + } + + public static void Information(this ILogger? logger, string message) + { + logger?.Log(LogLevel.Information, default, 0, null, (s, e) => message); + } + + public static void Warning(this ILogger? logger, string message) + { + if (logger is null) + { + System.Diagnostics.Debug.WriteLine($"[{DateTime.Now:hh:mm:ss.fff}] [IPC] [Warning] {message}"); + } + else + { + logger?.Log(LogLevel.Warning, default, 0, null, (s, e) => message); + } + } + + public static void Error(this ILogger? logger, string message) + { + if (logger is null) + { + System.Diagnostics.Debug.WriteLine($"[{DateTime.Now:hh:mm:ss.fff}] [IPC] [Error] {message}"); + } + else + { + logger?.Log(LogLevel.Error, default, 0, null, (s, e) => message); + } + } + + public static void Error(this ILogger? logger, Exception exception, string? message = null) + { + logger?.Log(LogLevel.Error, default, 0, exception, (s, e) => message ?? ""); + } + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/StreamExtensions.cs b/src/dotnetCampus.Ipc/Utils/Extensions/StreamExtensions.cs similarity index 93% rename from src/dotnetCampus.Ipc.PipeCore/Utils/StreamExtensions.cs rename to src/dotnetCampus.Ipc/Utils/Extensions/StreamExtensions.cs index 1a189ef0..42fdc292 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/StreamExtensions.cs +++ b/src/dotnetCampus.Ipc/Utils/Extensions/StreamExtensions.cs @@ -1,7 +1,7 @@ using System.IO; using System.Threading.Tasks; -namespace dotnetCampus.Ipc.PipeCore.Utils +namespace dotnetCampus.Ipc.Utils.Extensions { #if NETFRAMEWORK static class StreamExtensions diff --git a/src/dotnetCampus.Ipc/Utils/IO/AsyncBinaryReader.cs b/src/dotnetCampus.Ipc/Utils/IO/AsyncBinaryReader.cs new file mode 100644 index 00000000..5b140346 --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/IO/AsyncBinaryReader.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using dotnetCampus.Ipc.Utils.Buffers; + +namespace dotnetCampus.Ipc.Utils.IO +{ + class AsyncBinaryReader + { + public AsyncBinaryReader(Stream stream, ISharedArrayPool sharedArrayPool) + { + _sharedArrayPool = sharedArrayPool; + Stream = stream; + } + + private Stream Stream { get; } + private readonly ISharedArrayPool _sharedArrayPool; + + public Task> ReadUInt16Async() + { + return ReadAsync(2, byteList => BitConverter.ToUInt16(byteList, 0)); + } + + public Task> ReadReadUInt64Async() + { + return ReadAsync(sizeof(ulong), byteList => BitConverter.ToUInt64(byteList, 0)); + } + + public Task> ReadUInt32Async() + { + return ReadAsync(sizeof(uint), byteList => BitConverter.ToUInt32(byteList, 0)); + } + + private async Task> ReadAsync(int byteCount, Func converter) + { + var readResult = await InternalReadAsync(byteCount); + if (readResult.IsEndOfStream) + { + return StreamReadResult.EndOfStream; + } + + var byteList = readResult.Result; + + var result = converter(byteList); + _sharedArrayPool.Return(byteList); + return new StreamReadResult(result); + } + + private async Task> InternalReadAsync(int numBytes) + { + var byteList = _sharedArrayPool.Rent(numBytes); + var bytesRead = 0; + + do + { + var n = await Stream.ReadAsync(byteList, bytesRead, numBytes - bytesRead).ConfigureAwait(false); + if (n == 0) + { + return StreamReadResult.EndOfStream; + } + + bytesRead += n; + } while (bytesRead < numBytes); + + return new StreamReadResult(byteList); + } + } + + readonly struct StreamReadResult + { + public StreamReadResult(T result, bool isEndOfStream = false) + { + Result = result; + + IsEndOfStream = isEndOfStream; + } + + public static StreamReadResult EndOfStream => + new StreamReadResult(result: default!, isEndOfStream: true); + + public bool IsEndOfStream { get; } + + public T Result { get; } + } +} diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/AsyncBinaryWriter.cs b/src/dotnetCampus.Ipc/Utils/IO/AsyncBinaryWriter.cs similarity index 81% rename from src/dotnetCampus.Ipc.PipeCore/Utils/AsyncBinaryWriter.cs rename to src/dotnetCampus.Ipc/Utils/IO/AsyncBinaryWriter.cs index a954c56a..42564019 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/AsyncBinaryWriter.cs +++ b/src/dotnetCampus.Ipc/Utils/IO/AsyncBinaryWriter.cs @@ -2,7 +2,9 @@ using System.IO; using System.Threading.Tasks; -namespace dotnetCampus.Ipc.PipeCore.Utils +using dotnetCampus.Ipc.Utils.Extensions; + +namespace dotnetCampus.Ipc.Utils.IO { class AsyncBinaryWriter { @@ -15,22 +17,22 @@ public AsyncBinaryWriter(Stream stream) public async Task WriteAsync(ushort value) { - await Stream.WriteAsync(BitConverter.GetBytes(value)); + await Stream.WriteAsync(BitConverter.GetBytes(value)).ConfigureAwait(false); } public async Task WriteAsync(uint value) { - await Stream.WriteAsync(BitConverter.GetBytes(value)); + await Stream.WriteAsync(BitConverter.GetBytes(value)).ConfigureAwait(false); } public async Task WriteAsync(ulong value) { - await Stream.WriteAsync(BitConverter.GetBytes(value)); + await Stream.WriteAsync(BitConverter.GetBytes(value)).ConfigureAwait(false); } public async Task WriteAsync(int value) { - await Stream.WriteAsync(BitConverter.GetBytes(value)); + await Stream.WriteAsync(BitConverter.GetBytes(value)).ConfigureAwait(false); } } } diff --git a/src/dotnetCampus.Ipc.PipeCore/Utils/ByteListMessageStream.cs b/src/dotnetCampus.Ipc/Utils/IO/ByteListMessageStream.cs similarity index 87% rename from src/dotnetCampus.Ipc.PipeCore/Utils/ByteListMessageStream.cs rename to src/dotnetCampus.Ipc/Utils/IO/ByteListMessageStream.cs index f89ca3ff..d8e9c35d 100644 --- a/src/dotnetCampus.Ipc.PipeCore/Utils/ByteListMessageStream.cs +++ b/src/dotnetCampus.Ipc/Utils/IO/ByteListMessageStream.cs @@ -1,7 +1,9 @@ using System.IO; -using dotnetCampus.Ipc.PipeCore.Context; -namespace dotnetCampus.Ipc.PipeCore.Utils +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Buffers; + +namespace dotnetCampus.Ipc.Utils.IO { internal class ByteListMessageStream : MemoryStream { diff --git a/src/dotnetCampus.Ipc/Utils/Logging/EventId.cs b/src/dotnetCampus.Ipc/Utils/Logging/EventId.cs new file mode 100644 index 00000000..d517d50a --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/Logging/EventId.cs @@ -0,0 +1,90 @@ +namespace dotnetCampus.Ipc.Utils.Logging +{ + internal readonly struct EventId + { + /// + /// Implicitly creates an EventId from the given . + /// + /// The to convert to an EventId. + public static implicit operator EventId(int i) + { + return new EventId(i); + } + + /// + /// Checks if two specified instances have the same value. They are equal if they have the same Id. + /// + /// The first . + /// The second . + /// if the objects are equal. + public static bool operator ==(EventId left, EventId right) + { + return left.Equals(right); + } + + /// + /// Checks if two specified instances have different values. + /// + /// The first . + /// The second . + /// if the objects are not equal. + public static bool operator !=(EventId left, EventId right) + { + return !left.Equals(right); + } + + /// + /// Initializes an instance of the struct. + /// + /// The numeric identifier for this event. + /// The name of this event. + public EventId(int id, string? name = null) + { + Id = id; + Name = name; + } + + /// + /// Gets the numeric identifier for this event. + /// + public int Id { get; } + + /// + /// Gets the name of this event. + /// + public string? Name { get; } + + /// + public override string ToString() + { + return Name ?? Id.ToString(); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. Two events are equal if they have the same id. + /// + /// An object to compare with this object. + /// if the current object is equal to the other parameter; otherwise, . + public bool Equals(EventId other) + { + return Id == other.Id; + } + + /// + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + + return obj is EventId eventId && Equals(eventId); + } + + /// + public override int GetHashCode() + { + return Id; + } + } +} diff --git a/src/dotnetCampus.Ipc/Utils/Logging/ILogger.cs b/src/dotnetCampus.Ipc/Utils/Logging/ILogger.cs new file mode 100644 index 00000000..2355f2c7 --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/Logging/ILogger.cs @@ -0,0 +1,18 @@ +using System; + +namespace dotnetCampus.Ipc.Utils.Logging +{ + internal interface ILogger + { + /// + /// Writes a log entry. + /// + /// Entry will be written on this level. + /// Id of the event. + /// The entry to be written. Can be also an object. + /// The exception related to this entry. + /// Function to create a message of the and . + /// The type of the object to be written. + void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter); + } +} diff --git a/src/dotnetCampus.Ipc/Utils/Logging/IpcLogger.cs b/src/dotnetCampus.Ipc/Utils/Logging/IpcLogger.cs new file mode 100644 index 00000000..2ae2542a --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/Logging/IpcLogger.cs @@ -0,0 +1,40 @@ +using System; +using System.Diagnostics; + +namespace dotnetCampus.Ipc.Utils.Logging +{ + /// + /// 为 IPC 提供日志输出。 + /// + public class IpcLogger : ILogger + { + public IpcLogger(string name) + { + Name = name; + } + + private string Name { get; } + + void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + Log(logLevel, state, exception, formatter); + } + + protected virtual void Log(LogLevel logLevel, TState state, Exception? exception, Func formatter) + { + if (logLevel >= LogLevel.Information) + { + Debug.WriteLine(formatter(state, exception)); + } + } + + /// + /// 返回此日志的名字。 + /// + /// + public override string ToString() + { + return $"[{Name}]"; + } + } +} diff --git a/src/dotnetCampus.Ipc/Utils/Logging/LogLevel.cs b/src/dotnetCampus.Ipc/Utils/Logging/LogLevel.cs new file mode 100644 index 00000000..18ee9d14 --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/Logging/LogLevel.cs @@ -0,0 +1,45 @@ +namespace dotnetCampus.Ipc.Utils.Logging +{ + public enum LogLevel + { + /// + /// Logs that contain the most detailed messages. These messages may contain sensitive application data. + /// These messages are disabled by default and should never be enabled in a production environment. + /// + Trace = 0, + + /// + /// Logs that are used for interactive investigation during development. These logs should primarily contain + /// information useful for debugging and have no long-term value. + /// + Debug = 1, + + /// + /// Logs that track the general flow of the application. These logs should have long-term value. + /// + Information = 2, + + /// + /// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the + /// application execution to stop. + /// + Warning = 3, + + /// + /// Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a + /// failure in the current activity, not an application-wide failure. + /// + Error = 4, + + /// + /// Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires + /// immediate attention. + /// + Critical = 5, + + /// + /// Not used for writing log messages. Specifies that a logging category should not write any messages. + /// + None = 6, + } +} diff --git a/src/dotnetCampus.Ipc/Utils/TaskUtils.cs b/src/dotnetCampus.Ipc/Utils/TaskUtils.cs new file mode 100644 index 00000000..69b4301a --- /dev/null +++ b/src/dotnetCampus.Ipc/Utils/TaskUtils.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace dotnetCampus.Ipc.Utils +{ + internal static class TaskUtils + { + public static async Task As(this Task sourceTask) where TSource : TTarget + { + return await sourceTask.ConfigureAwait(false); + } + } +} diff --git a/src/dotnetCampus.Ipc/dotnetCampus.Ipc.csproj b/src/dotnetCampus.Ipc/dotnetCampus.Ipc.csproj index d3d7f072..027b2efc 100644 --- a/src/dotnetCampus.Ipc/dotnetCampus.Ipc.csproj +++ b/src/dotnetCampus.Ipc/dotnetCampus.Ipc.csproj @@ -1,10 +1,10 @@ - + - netcoreapp3.1;netstandard2.0;net45 - + netcoreapp3.1;net45 true enable + True @@ -28,17 +28,20 @@ snupkg - + - + - - + + + + + diff --git a/src/dotnetCampus.Ipc/dotnetCampus.Ipc.csproj.DotSettings b/src/dotnetCampus.Ipc/dotnetCampus.Ipc.csproj.DotSettings deleted file mode 100644 index edc7d0bc..00000000 --- a/src/dotnetCampus.Ipc/dotnetCampus.Ipc.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/tests/dotnetCampus.Ipc.Tests/AckManagerTest.cs b/tests/dotnetCampus.Ipc.Tests/AckManagerTest.cs index e6d41aea..0c4860f3 100644 --- a/tests/dotnetCampus.Ipc.Tests/AckManagerTest.cs +++ b/tests/dotnetCampus.Ipc.Tests/AckManagerTest.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; -using dotnetCampus.Ipc.PipeCore.Context; using Microsoft.VisualStudio.TestTools.UnitTesting; using MSTest.Extensions.Contracts; diff --git a/tests/dotnetCampus.Ipc.Tests/Attributes.cs b/tests/dotnetCampus.Ipc.Tests/Attributes.cs index 17f616b5..1c801b25 100644 --- a/tests/dotnetCampus.Ipc.Tests/Attributes.cs +++ b/tests/dotnetCampus.Ipc.Tests/Attributes.cs @@ -1,3 +1,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("dotnetCampus.Ipc.Tests")] +[assembly: InternalsVisibleTo("dotnetCampus.Ipc.Demo")] +[assembly: InternalsVisibleTo("dotnetCampus.Ipc.WpfDemo")] diff --git a/tests/dotnetCampus.Ipc.Tests/IpcMessageConverterTest.cs b/tests/dotnetCampus.Ipc.Tests/IpcMessageConverterTest.cs index a96ea77f..d8516e49 100644 --- a/tests/dotnetCampus.Ipc.Tests/IpcMessageConverterTest.cs +++ b/tests/dotnetCampus.Ipc.Tests/IpcMessageConverterTest.cs @@ -1,8 +1,12 @@ using System.IO; -using dotnetCampus.Ipc.PipeCore; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.Utils; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils.Buffers; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using MSTest.Extensions.Contracts; namespace dotnetCampus.Ipc.Tests @@ -22,13 +26,13 @@ public void IpcMessageConverterWriteAsync() var messageHeader = new byte[] { 0x00, 0x00 }; var breakMessageHeader = new byte[] { 0x00, 0x01 }; - await IpcMessageConverter.WriteAsync(memoryStream, messageHeader, ack, IpcMessageCommandType.Unknown, - buffer, 0, - buffer.Length, "test", null!); + var ipcBufferMessageContext = new IpcBufferMessageContext("test", IpcMessageCommandType.Unknown, new IpcMessageBody(buffer)); + + await IpcMessageConverter.WriteAsync(memoryStream, messageHeader, ack, ipcBufferMessageContext); memoryStream.Position = 0; - var ipcMessageResult = await IpcMessageConverter.ReadAsync(memoryStream, - breakMessageHeader, new SharedArrayPool()); + var ipcMessageResult = (await IpcMessageConverter.ReadAsync(memoryStream, + breakMessageHeader, new SharedArrayPool())).Result; var success = ipcMessageResult.Success; var ipcMessageCommandType = ipcMessageResult.IpcMessageCommandType; @@ -44,13 +48,14 @@ await IpcMessageConverter.WriteAsync(memoryStream, messageHeader, ack, IpcMessag ulong ack = 10; var buffer = new byte[] { 0x12, 0x12, 0x00 }; var messageHeader = new byte[] { 0x00, 0x00 }; - await IpcMessageConverter.WriteAsync(memoryStream, messageHeader, ack, IpcMessageCommandType.Unknown, - buffer, 0, - buffer.Length, "test", null!); + + var ipcBufferMessageContext = new IpcBufferMessageContext("test", IpcMessageCommandType.Unknown, new IpcMessageBody(buffer)); + + await IpcMessageConverter.WriteAsync(memoryStream, messageHeader, ack, ipcBufferMessageContext); memoryStream.Position = 0; - var ipcMessageResult = await IpcMessageConverter.ReadAsync(memoryStream, - ipcConfiguration.MessageHeader, new SharedArrayPool()); + var ipcMessageResult = (await IpcMessageConverter.ReadAsync(memoryStream, + ipcConfiguration.MessageHeader, new SharedArrayPool())).Result; var success = ipcMessageResult.Success; var ipcMessageCommandType = ipcMessageResult.IpcMessageCommandType; @@ -66,13 +71,12 @@ await IpcMessageConverter.WriteAsync(memoryStream, messageHeader, ack, IpcMessag var ipcConfiguration = new IpcConfiguration(); ulong ack = 10; var buffer = new byte[] { 0x12, 0x12, 0x00 }; - await IpcMessageConverter.WriteAsync(memoryStream, ipcConfiguration.MessageHeader, ack, - IpcMessageCommandType.Unknown, buffer, 0, - buffer.Length, "test", null!); + var ipcBufferMessageContext = new IpcBufferMessageContext("test", IpcMessageCommandType.Unknown, new IpcMessageBody(buffer)); + await IpcMessageConverter.WriteAsync(memoryStream, ipcConfiguration.MessageHeader, ack, ipcBufferMessageContext); memoryStream.Position = 0; - var (success, ipcMessageContext) = await IpcMessageConverter.ReadAsync(memoryStream, - ipcConfiguration.MessageHeader, new SharedArrayPool()); + var (success, ipcMessageContext) = (await IpcMessageConverter.ReadAsync(memoryStream, + ipcConfiguration.MessageHeader, new SharedArrayPool())).Result; Assert.AreEqual(true, success); Assert.AreEqual(ack, ipcMessageContext.Ack.Value); diff --git a/tests/dotnetCampus.Ipc.Tests/IpcObjectJsonSerializerTests.cs b/tests/dotnetCampus.Ipc.Tests/IpcObjectJsonSerializerTests.cs index 3199f87d..5e261d62 100644 --- a/tests/dotnetCampus.Ipc.Tests/IpcObjectJsonSerializerTests.cs +++ b/tests/dotnetCampus.Ipc.Tests/IpcObjectJsonSerializerTests.cs @@ -1,6 +1,8 @@ -using dotnetCampus.Ipc.Abstractions; +using dotnetCampus.Ipc.Serialization; using dotnetCampus.Ipc.Utils; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using MSTest.Extensions.Contracts; namespace dotnetCampus.Ipc.Tests diff --git a/tests/dotnetCampus.Ipc.Tests/PeerProxyTest.cs b/tests/dotnetCampus.Ipc.Tests/PeerProxyTest.cs new file mode 100644 index 00000000..7aad6df7 --- /dev/null +++ b/tests/dotnetCampus.Ipc.Tests/PeerProxyTest.cs @@ -0,0 +1,377 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Exceptions; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Pipes; +using dotnetCampus.Ipc.Utils.Extensions; +using dotnetCampus.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MSTest.Extensions.Contracts; + +namespace dotnetCampus.Ipc.Tests +{ + [TestClass] + public class PeerProxyTest + { + [ContractTestCase] + public void SendWithReconnect() + { + "断开连接过程中,所有请求响应,都可以在重连之后请求成功".Test(async () => + { + var name = "B_PeerReconnected"; + var aRequest = new byte[] { 0xF1 }; + var cResponse = new byte[] { 0xF1, 0xF2 }; + + var a = new IpcProvider("A_PeerReconnected", new IpcConfiguration() + { + AutoReconnectPeers = true, + }); + // 毕竟一会就要挂了,啥都不需要配置 + var b = new IpcProvider(name); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + + // 连接成功了,那么就让 b 凉凉 + b.Dispose(); + + // 等待后台所有断开成功 + await Task.Delay(TimeSpan.FromSeconds(2)); + + // 预期状态是 Peer 是断开的,等待重新连接 + Assert.AreEqual(true, peer.IsBroken); + Assert.AreEqual(false, peer.WaitForFinishedTaskCompletionSource.Task.IsCompleted); + + // 开始请求响应,预期进入等待,没有任何响应 + var requestTask = peer.GetResponseAsync(new IpcMessage("A发送", aRequest)); + + // 重新启动 b 服务,用法是再新建一个 c 用了 b 的 name 从而假装是 b 重启 + var c = new IpcProvider(name, new IpcConfiguration() + { + DefaultIpcRequestHandler = new DelegateIpcRequestHandler(context => + new IpcHandleRequestMessageResult(new IpcMessage("C回复", cResponse))) + }); + c.StartServer(); + + // 预期可以收到 + await requestTask.WaitTimeout(TimeSpan.FromSeconds(3)); + var ipcMessage = await requestTask; + Assert.AreEqual(true, ipcMessage.Body.AsSpan().SequenceEqual(cResponse)); + }); + + "断开连接过程中,发送的所有消息,都可以在重连之后发送".Test(async () => + { + var name = "B_PeerReconnected"; + var aRequest = new byte[] { 0xF1 }; + + var a = new IpcProvider("A_PeerReconnected", new IpcConfiguration() + { + AutoReconnectPeers = true, + }); + // 毕竟一会就要挂了,啥都不需要配置 + var b = new IpcProvider(name); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + + // 连接成功了,那么就让 b 凉凉 + b.Dispose(); + + // 等待后台所有断开成功 + await Task.Delay(TimeSpan.FromSeconds(2)); + + // 预期状态是 Peer 是断开的,等待重新连接 + Assert.AreEqual(true, peer.IsBroken); + Assert.AreEqual(false, peer.WaitForFinishedTaskCompletionSource.Task.IsCompleted); + + // 开始发送消息,此时发送消息的任务都在进入等待 + var notifyTask = peer.NotifyAsync(new IpcMessage("A发送", aRequest)); + + // 稍微等一下,此时预期还是没有发送完成 + await Task.WhenAny(notifyTask, Task.Delay(TimeSpan.FromMilliseconds(100))); + Assert.AreEqual(false, notifyTask.IsCompleted); + + // 重新启动 b 服务,用法是再新建一个 c 用了 b 的 name 从而假装是 b 重启 + var c = new IpcProvider(name); + + // 是否可以收到重新发送消息 + var receiveANotifyTask = new TaskCompletionSource(); + c.PeerConnected += (s, e) => + { + e.Peer.MessageReceived += (sender, args) => + { + if (args.Message.Body.AsSpan().SequenceEqual(aRequest)) + { + receiveANotifyTask.SetResult(true); + } + }; + }; + + c.StartServer(); + + await receiveANotifyTask.Task.WaitTimeout(); + // 发送成功 + Assert.AreEqual(true, notifyTask.IsCompleted); + Assert.AreEqual(true, receiveANotifyTask.Task.IsCompleted); + }); + } + + [ContractTestCase] + public void GetResponseAsync() + { + "向对方请求响应,可以拿到对方的回复".Test(async () => + { + var name = "B_PeerReconnected"; + var aRequest = new byte[] { 0xF1 }; + var bResponse = new byte[] { 0xF1, 0xF2 }; + + var a = new IpcProvider("A_PeerReconnected"); + var b = new IpcProvider(name, new IpcConfiguration() + { + DefaultIpcRequestHandler = new DelegateIpcRequestHandler(context => + new IpcHandleRequestMessageResult(new IpcMessage("B回复", bResponse))) + }); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + var request1 = await peer.GetResponseAsync(new IpcMessage("A发送", aRequest)); + Assert.AreEqual(true, bResponse.AsSpan().SequenceEqual(request1.Body.AsSpan())); + + // 多次发送消息测试一下 + var request2 = await peer.GetResponseAsync(new IpcMessage("A发送", aRequest)); + Assert.AreEqual(true, bResponse.AsSpan().SequenceEqual(request2.Body.AsSpan())); + }); + } + + [ContractTestCase] + public void PeerReconnected() + { + "连接过程中,对方断掉重连,可以收到重连事件".Test(async () => + { + var name = "B_PeerReconnected"; + var a = new IpcProvider("A_PeerReconnected", new IpcConfiguration() + { + AutoReconnectPeers = true + }); + var b = new IpcProvider(name); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + var peerReconnectedTask = new TaskCompletionSource(); + peer.PeerReconnected += delegate + { + peerReconnectedTask.SetResult(true); + }; + + // 断开 b 此时只会收到断开消息,不会收到重连消息 + b.Dispose(); + + await Task.Yield(); + // 判断此时是否收到重连消息 + Assert.AreEqual(false, peerReconnectedTask.Task.IsCompleted); + + // 重新启动 b 服务,用法是再新建一个 c 用了 b 的 name 从而假装是 b 重启 + var c = new IpcProvider(name); + c.StartServer(); + + // 多线程,需要等待一下,等待连接 + await Task.WhenAny(peerReconnectedTask.Task, Task.Delay(TimeSpan.FromSeconds(3))); + + if (!peerReconnectedTask.Task.IsCompleted) + { +#if DEBUG + // 进入断点,也许上面的时间太短 + await Task.WhenAny(peerReconnectedTask.Task, Task.Delay(TimeSpan.FromMinutes(5))); +#endif + } + + Assert.AreEqual(true, peerReconnectedTask.Task.IsCompleted); + Assert.AreEqual(true, peerReconnectedTask.Task.Result); + + Assert.AreEqual(true, peer.IsConnectedFinished); + Assert.AreEqual(false, peer.IsBroken); + }); + } + + [ContractTestCase] + public void PeerConnectionBroken() + { + "发送请求响应,对方断掉,请求将会抛出异常".Test(async () => + { + var name = "B_BreakAllRequestTaskByIpcBroken"; + var aRequest = new byte[] { 0xF2 }; + var asyncManualResetEvent = new AsyncManualResetEvent(false); + var a = new IpcProvider("A_BreakAllRequestTaskByIpcBroken"); + var b = new IpcProvider(name, + new IpcConfiguration() + { + DefaultIpcRequestHandler = new DelegateIpcRequestHandler(context => + Task.Run(async () => + { + await asyncManualResetEvent.WaitOneAsync(); + return (IIpcResponseMessage) null; + })) + }); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + + // 发送请求,预期这些请求都没有收到回复 + var taskList = new List>(); + for (int i = 0; i < 10; i++) + { + Task responseTask = peer.GetResponseAsync(new IpcMessage("A发送", aRequest)); + taskList.Add(responseTask); + } + + await Task.Yield(); + + foreach (var task in taskList) + { + Assert.AreEqual(false, task.IsCompleted); + } + + // 让消息写入一下 + await Task.Delay(TimeSpan.FromSeconds(2)); + + b.Dispose(); + + // 等待断开 + await Task.Delay(TimeSpan.FromSeconds(5)); + foreach (var task in taskList) + { + Assert.AreEqual(true, task.IsCompleted); + + // 这里的异常也许是 连接断开异常, 也许是写入过程中,对方已断开异常 + Assert.IsNotNull(task.Exception?.InnerExceptions[0] as Exception); + } + + // 所有请求都炸掉 + Assert.AreEqual(0, peer.IpcMessageRequestManager.WaitingResponseCount); + }); + + "连接过程中,对方断掉,可以收到对方断掉的消息".Test(async () => + { + // 让 a 去连接 b 然后聊聊天 + // 接着将 b 结束,此时 a 的 peer 将会断开连接 + var name = "B_PeerConnectionBroken"; + var aRequest = new byte[] { 0xF1 }; + var bResponse = new byte[] { 0xF1, 0xF2 }; + + var a = new IpcProvider("A_PeerConnectionBroken"); + var b = new IpcProvider(name, + new IpcConfiguration() + { + DefaultIpcRequestHandler = new DelegateIpcRequestHandler(context => + new IpcHandleRequestMessageResult(new IpcMessage("B回复", bResponse))) + }); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + var request1 = await peer.GetResponseAsync(new IpcMessage("A发送", aRequest)); + Assert.AreEqual(true, bResponse.AsSpan().SequenceEqual(request1.Body.AsSpan())); + await Task.Yield(); + + var peerBrokenTask = new TaskCompletionSource(); + peer.PeerConnectionBroken += delegate { peerBrokenTask.TrySetResult(true); }; + + b.Dispose(); + + // 预期 b 结束时,能收到 PeerConnectionBroken 事件 + await Task.WhenAny(peerBrokenTask.Task, Task.Delay(TimeSpan.FromSeconds(2))); + + if (!peerBrokenTask.Task.IsCompleted) + { +#if DEBUG + // 进入断点,也许上面的时间太短 + await Task.WhenAny(peerBrokenTask.Task, Task.Delay(TimeSpan.FromMinutes(5))); +#endif + } + + // 判断是否能收到对方断开的消息 + Assert.AreEqual(true, peerBrokenTask.Task.IsCompleted); + Assert.AreEqual(true, peerBrokenTask.Task.Result); + + Assert.AreEqual(true, peer.IsBroken); + }); + } + + [ContractTestCase] + public void Dispose() + { + "使用释放的服务发送消息,将会提示对象释放".Test(async () => + { + var name = "B_PeerConnectionBroken"; + + var aRequest = new byte[] { 0xF1 }; + var a = new IpcProvider("A_PeerConnectionBroken", new IpcConfiguration() + { + AutoReconnectPeers = true + }); + var b = new IpcProvider(name); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + + // 设置为自动重连的服务,释放 + a.Dispose(); + + // 等待资源的释放 + await Task.Delay(TimeSpan.FromSeconds(2)); + + await Assert.ThrowsExceptionAsync(async () => + { + await peer.NotifyAsync(new IpcMessage("A发送", aRequest)); + }); + }); + + "设置为自动重连的服务,释放之后,不会有任何资源进入等待".Test(async () => + { + var name = "B_PeerConnectionBroken"; + + var a = new IpcProvider("A_PeerConnectionBroken", new IpcConfiguration() + { + AutoReconnectPeers = true + }); + var b = new IpcProvider(name); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + + // 设置为自动重连的服务,释放 + a.Dispose(); + + // 等待资源的释放 + await Task.Delay(TimeSpan.FromSeconds(2)); +#if DEBUG + for (int i = 0; i < 1000; i++) + { + await Task.Delay(TimeSpan.FromSeconds(1)); + } +#endif + + Assert.AreEqual(true, peer.IsBroken); + Assert.AreEqual(true, peer.WaitForFinishedTaskCompletionSource.Task.IsCompleted); + }); + } + } +} diff --git a/tests/dotnetCampus.Ipc.Tests/PeerReConnectorTest.cs b/tests/dotnetCampus.Ipc.Tests/PeerReConnectorTest.cs new file mode 100644 index 00000000..ffe3f7bd --- /dev/null +++ b/tests/dotnetCampus.Ipc.Tests/PeerReConnectorTest.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Castle.DynamicProxy.Generators.Emitters.SimpleAST; +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Pipes; +using dotnetCampus.Ipc.Utils.Extensions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MSTest.Extensions.Contracts; + +namespace dotnetCampus.Ipc.Tests +{ + [TestClass] + public class PeerReConnectorTest + { + [ContractTestCase] + public void Reconnect() + { + "连接过程中,对方断掉,可以自动重新连接对方".Test(async () => + { + // 让 a 去连接 b 然后聊聊天 + // 接着将 b 结束,此时 a 的 peer 将会断开连接 + // 然后启动 c 让 c 用原本 b 的 name 从而假装是 b 重启 + // 预期是 a 会重新连接到 "b" 继续聊天 + var name = "B_PeerReConnectorTest"; + var aRequest = new byte[] { 0xF1 }; + var bResponse = new byte[] { 0xF1, 0xF2 }; + var cResponse = new byte[] { 0x01, 0x05 }; + + var a = new IpcProvider("A_PeerReConnectorTest", new IpcConfiguration() + { + AutoReconnectPeers = true + }); + var b = new IpcProvider(name, + new IpcConfiguration() + { + DefaultIpcRequestHandler = new DelegateIpcRequestHandler(context => + new IpcHandleRequestMessageResult(new IpcMessage("B回复", bResponse))) + }); + + a.StartServer(); + b.StartServer(); + + var peer = await a.GetAndConnectToPeerAsync(name); + var request1 = await peer.GetResponseAsync(new IpcMessage("A发送", aRequest)); + Assert.AreEqual(true, bResponse.AsSpan().SequenceEqual(request1.Body.AsSpan())); + await Task.Yield(); + + var peerBrokenTask = new TaskCompletionSource(); + peer.PeerConnectionBroken += delegate { peerBrokenTask.TrySetResult(true); }; + + b.Dispose(); + + // 预期 b 结束时,能收到 PeerConnectionBroken 事件 + await Task.WhenAny(peerBrokenTask.Task, Task.Delay(TimeSpan.FromSeconds(2))); + + if (!peerBrokenTask.Task.IsCompleted) + { +#if DEBUG + // 进入断点,也许上面的时间太短 + await Task.WhenAny(peerBrokenTask.Task, Task.Delay(TimeSpan.FromMinutes(5))); +#endif + } + + // 判断是否能收到对方断开的消息 + Assert.AreEqual(true, peerBrokenTask.Task.IsCompleted); + Assert.AreEqual(true, peerBrokenTask.Task.Result); + + Assert.AreEqual(true, peer.IsBroken); + Assert.AreEqual(false, peer.WaitForFinishedTaskCompletionSource.Task.IsCompleted); + + // 启动 c 用来假装 b 重启,能让 a 自动用原先的 Peer 连接 + var c = new IpcProvider(name, + new IpcConfiguration() + { + DefaultIpcRequestHandler = new DelegateIpcRequestHandler(context => + new IpcHandleRequestMessageResult(new IpcMessage("C回复", cResponse))) + }); + c.StartServer(); + + // 等待 a 重新连接 + await Task.WhenAny(peer.WaitForFinishedTaskCompletionSource.Task, Task.Delay(TimeSpan.FromSeconds(2))); + if (!peer.WaitForFinishedTaskCompletionSource.Task.IsCompleted) + { +#if DEBUG + // 进入断点,也许上面的时间太短 + await Task.WhenAny(peer.WaitForFinishedTaskCompletionSource.Task, Task.Delay(TimeSpan.FromMinutes(2))); +#endif + } + + var request2 = await peer.GetResponseAsync(new IpcMessage("A发送", aRequest)); + Assert.AreEqual(true, cResponse.AsSpan().SequenceEqual(request2.Body.AsSpan())); + }); + } + } + + +} diff --git a/tests/dotnetCampus.Ipc.Tests/PeerRegisterProviderTests.cs b/tests/dotnetCampus.Ipc.Tests/PeerRegisterProviderTests.cs index bdeb7d49..f5530372 100644 --- a/tests/dotnetCampus.Ipc.Tests/PeerRegisterProviderTests.cs +++ b/tests/dotnetCampus.Ipc.Tests/PeerRegisterProviderTests.cs @@ -1,8 +1,13 @@ using System.IO; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Internals; using dotnetCampus.Ipc.PipeCore; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.Utils; +using dotnetCampus.Ipc.Utils.Buffers; +using dotnetCampus.Ipc.Utils.IO; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using MSTest.Extensions.Contracts; namespace dotnetCampus.Ipc.Tests @@ -23,7 +28,7 @@ public void BuildPeerRegisterMessage() foreach (var ipcBufferMessage in bufferMessageContext.IpcBufferMessageList) { - memoryStream.Write(ipcBufferMessage.Buffer, ipcBufferMessage.Start, ipcBufferMessage.Count); + memoryStream.Write(ipcBufferMessage.Buffer, ipcBufferMessage.Start, ipcBufferMessage.Length); } // 写入其他内容 @@ -63,12 +68,12 @@ public void BuildPeerRegisterMessage() var memoryStream = new MemoryStream(bufferMessageContext.Length); var ipcConfiguration = new IpcConfiguration(); - await IpcMessageConverter.WriteAsync(memoryStream, ipcConfiguration.MessageHeader, 10, - bufferMessageContext, null!); + await IpcMessageConverter.WriteAsync(memoryStream, ipcConfiguration.MessageHeader, ack: 10, + bufferMessageContext); memoryStream.Position = 0; - var (success, ipcMessageContext) = await IpcMessageConverter.ReadAsync(memoryStream, - ipcConfiguration.MessageHeader, new SharedArrayPool()); + var (success, ipcMessageContext) = (await IpcMessageConverter.ReadAsync(memoryStream, + ipcConfiguration.MessageHeader, new SharedArrayPool())).Result; Assert.AreEqual(true, success); @@ -90,7 +95,7 @@ await IpcMessageConverter.WriteAsync(memoryStream, ipcConfiguration.MessageHeade foreach (var ipcBufferMessage in bufferMessageContext.IpcBufferMessageList) { - memoryStream.Write(ipcBufferMessage.Buffer, ipcBufferMessage.Start, ipcBufferMessage.Count); + memoryStream.Write(ipcBufferMessage.Buffer, ipcBufferMessage.Start, ipcBufferMessage.Length); } memoryStream.Position = 0; diff --git a/tests/dotnetCampus.Ipc.Tests/ResponseManagerTests.cs b/tests/dotnetCampus.Ipc.Tests/ResponseManagerTests.cs index 0232bccf..ff22b0f6 100644 --- a/tests/dotnetCampus.Ipc.Tests/ResponseManagerTests.cs +++ b/tests/dotnetCampus.Ipc.Tests/ResponseManagerTests.cs @@ -1,13 +1,17 @@ using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using dotnetCampus.Ipc.Abstractions; -using dotnetCampus.Ipc.Abstractions.Context; + +using dotnetCampus.Ipc.Context; +using dotnetCampus.Ipc.Internals; +using dotnetCampus.Ipc.Messages; using dotnetCampus.Ipc.PipeCore; -using dotnetCampus.Ipc.PipeCore.Context; -using dotnetCampus.Ipc.PipeCore.IpcPipe; -using dotnetCampus.Ipc.PipeCore.Utils.Extensions; +using dotnetCampus.Ipc.Pipes; +using dotnetCampus.Ipc.Utils.Extensions; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using MSTest.Extensions.Contracts; namespace dotnetCampus.Ipc.Tests @@ -31,12 +35,12 @@ public void SendAndGetResponse() DefaultIpcRequestHandler = new DelegateIpcRequestHandler(c => { Assert.AreEqual(ipcAName, c.Peer.PeerName); - c.Handle = true; - var span = c.IpcBufferMessage.AsSpan(); + c.Handled = true; + var span = c.IpcBufferMessage.Body.AsSpan(); Assert.AreEqual(true, span.SequenceEqual(requestByteList)); - return new IpcHandleRequestMessageResult(new IpcRequestMessage("Return", - new IpcBufferMessage(responseByteList))); + return new IpcHandleRequestMessageResult(new IpcMessage("Return", + new IpcMessageBody(responseByteList))); }) }); ipcA.StartServer(); @@ -45,8 +49,8 @@ public void SendAndGetResponse() var bPeer = await ipcA.GetAndConnectToPeerAsync(ipcBName); // 从 A 发送消息给到 B 然后可以收到从 B 返回的消息 var response = - await bPeer.GetResponseAsync(new IpcRequestMessage("发送", new IpcBufferMessage(requestByteList))); - Assert.AreEqual(true, response.AsSpan().SequenceEqual(responseByteList)); + await bPeer.GetResponseAsync(new IpcMessage("发送", new IpcMessageBody(requestByteList))); + Assert.AreEqual(true, response.Body.AsSpan().SequenceEqual(responseByteList)); }); } @@ -57,7 +61,7 @@ public void GetResponseAsync() { var ipcMessageRequestManager = new IpcMessageRequestManager(); var requestByteList = new byte[] { 0xFF, 0xFE }; - var request = new IpcRequestMessage("Tests", new IpcBufferMessage(requestByteList)); + var request = new IpcMessage("Tests", new IpcMessageBody(requestByteList)); var ipcClientRequestMessage = ipcMessageRequestManager.CreateRequestMessage(request); Assert.AreEqual(false, ipcClientRequestMessage.Task.IsCompleted); @@ -70,7 +74,7 @@ public void GetResponseAsync() }; Assert.IsNotNull(requestStream); - ipcMessageRequestManager.OnReceiveMessage(new PeerMessageArgs("Foo", requestStream, ack: 100, + ipcMessageRequestManager.OnReceiveMessage(new PeerStreamMessageArgs(new IpcMessageContext(), "Foo", requestStream, ack: 100, IpcMessageCommandType.RequestMessage)); Assert.IsNotNull(ipcClientRequestArgs); @@ -78,21 +82,100 @@ public void GetResponseAsync() var ipcMessageResponseManager = new IpcMessageResponseManager(); var responseMessageContext = ipcMessageResponseManager.CreateResponseMessage( ipcClientRequestArgs.MessageId, - new IpcRequestMessage("Tests", new IpcBufferMessage(responseByteList))); + new IpcMessage("Tests", new IpcMessageBody(responseByteList))); var responseStream = IpcBufferMessageContextToStream(responseMessageContext); - ipcMessageRequestManager.OnReceiveMessage(new PeerMessageArgs("Foo", responseStream, ack: 100, + ipcMessageRequestManager.OnReceiveMessage(new PeerStreamMessageArgs(new IpcMessageContext(), "Foo", responseStream, ack: 100, IpcMessageCommandType.ResponseMessage)); Assert.AreEqual(true, ipcClientRequestMessage.Task.IsCompleted); }); } - private static Stream IpcBufferMessageContextToStream(IpcBufferMessageContext ipcBufferMessageContext) + [ContractTestCase] + public void WaitingResponseCount() + { + "所有发送消息都收到回复后,将清空等待响应的数量".Test(() => + { + // 请求的顺序是 + // A: 生成请求消息 + // A: 发送请求消息 + // B: 收到请求消息 + // B: 生成回复消息 + // B: 发送回复消息 + // A: 收到回复消息 + // A: 完成请求 + var aIpcMessageRequestManager = new IpcMessageRequestManager(); + var requestByteList = new byte[] { 0xFF, 0xFE }; + var request = new IpcMessage("Tests", new IpcMessageBody(requestByteList)); + + var ipcClientRequestMessageList = new List(); + + for (int i = 0; i < 10; i++) + { + // 创建请求消息 + IpcClientRequestMessage ipcClientRequestMessage = aIpcMessageRequestManager.CreateRequestMessage(request); + ipcClientRequestMessageList.Add(ipcClientRequestMessage); + + Assert.AreEqual(i + 1, aIpcMessageRequestManager.WaitingResponseCount); + } + + // 创建的请求消息还没发送出去,需要进行发送 + // 发送的做法就是往 B 里面调用接收方法 + // 在测试里面不引入 IPC 的发送逻辑,因此 A 的发送就是调用 B 的接收 + var bIpcMessageRequestManager = new IpcMessageRequestManager(); + var bIpcMessageResponseManager = new IpcMessageResponseManager(); + + // 接收 B 的消息,用的是事件 + var ipcClientRequestArgsList = new List(); + bIpcMessageRequestManager.OnIpcClientRequestReceived += (sender, args) => + { + ipcClientRequestArgsList.Add(args); + }; + + // 开始发送消息 + foreach (var ipcClientRequestMessage in ipcClientRequestMessageList) + { + var requestStream = IpcBufferMessageContextToStream(ipcClientRequestMessage.IpcBufferMessageContext); + var args = new PeerStreamMessageArgs(new IpcMessageContext(), "Foo", requestStream, ack: 100, + IpcMessageCommandType.RequestMessage); + + bIpcMessageRequestManager.OnReceiveMessage(args); + } + + // 因为 A 发送了 10 条消息,因此 B 需要接收到 10 条 + Assert.AreEqual(ipcClientRequestMessageList.Count, ipcClientRequestArgsList.Count); + + // 逐条消息回复 + foreach (var ipcClientRequestArgs in ipcClientRequestArgsList) + { + var responseByteList = new byte[] { 0xF1, 0xF2 }; + var responseMessageContext = bIpcMessageResponseManager.CreateResponseMessage( + ipcClientRequestArgs.MessageId, + new IpcMessage("Tests", new IpcMessageBody(responseByteList))); + var responseStream = IpcBufferMessageContextToStream(responseMessageContext); + + // 回复就是发送消息给 A 相当于让 A 接收消息 + aIpcMessageRequestManager.OnReceiveMessage(new PeerStreamMessageArgs(new IpcMessageContext(), "Foo", responseStream, ack: 100, + IpcMessageCommandType.ResponseMessage)); + } + + // 此时 A 没有等待回复的消息 + Assert.AreEqual(0, aIpcMessageRequestManager.WaitingResponseCount); + // 所有发送的消息都收到回复 + + foreach (var ipcClientRequestMessage in ipcClientRequestMessageList) + { + Assert.AreEqual(true, ipcClientRequestMessage.Task.IsCompleted); + } + }); + } + + private static MemoryStream IpcBufferMessageContextToStream(IpcBufferMessageContext ipcBufferMessageContext) { var stream = new MemoryStream(); foreach (var ipcBufferMessage in ipcBufferMessageContext.IpcBufferMessageList) { - stream.Write(ipcBufferMessage.Buffer, ipcBufferMessage.Start, ipcBufferMessage.Count); + stream.Write(ipcBufferMessage.Buffer, ipcBufferMessage.Start, ipcBufferMessage.Length); } stream.Position = 0; diff --git a/tests/dotnetCampus.Ipc.Tests/TaskExtension.cs b/tests/dotnetCampus.Ipc.Tests/TaskExtension.cs new file mode 100644 index 00000000..4e97203e --- /dev/null +++ b/tests/dotnetCampus.Ipc.Tests/TaskExtension.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; + +namespace dotnetCampus.Ipc.Tests +{ + static class TaskExtension + { + public static async Task WaitTimeout(this Task task, TimeSpan? timeout = null) + { + timeout ??= TimeSpan.FromSeconds(2); + await Task.WhenAny(task, Task.Delay(timeout.Value)); + +#if DEBUG + // 进入断点,也许上面的时间太短 + if (!task.IsCompleted) + { + await Task.WhenAny(task, Task.Delay(TimeSpan.FromMinutes(5))); + } +#endif + } + } +} diff --git a/tests/dotnetCampus.Ipc.Tests/Utils/IO/AsyncBinaryReaderTests.cs b/tests/dotnetCampus.Ipc.Tests/Utils/IO/AsyncBinaryReaderTests.cs new file mode 100644 index 00000000..1fd0c2c0 --- /dev/null +++ b/tests/dotnetCampus.Ipc.Tests/Utils/IO/AsyncBinaryReaderTests.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using dotnetCampus.Ipc.Utils.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using dotnetCampus.Ipc.Utils.Buffers; +using MSTest.Extensions.Contracts; + +namespace dotnetCampus.Ipc.Utils.IO.Tests +{ + [TestClass()] + public class AsyncBinaryReaderTests + { + [ContractTestCase] + public void AsyncBinaryReaderTest() + { + "给定的共享数组远远超过所需长度,可以成功读取正确的数值".Test(async () => + { + var memoryStream = new MemoryStream(); + var streamWriter = new BinaryWriter(memoryStream); + ushort n1 = 15; + streamWriter.Write(n1); + ulong n2 = 65521; + streamWriter.Write(n2); + uint n3 = 0; + streamWriter.Write(n3); + streamWriter.Flush(); + memoryStream.Position = 0; + + var asyncBinaryReader = new AsyncBinaryReader(memoryStream, new FakeSharedArrayPool()); + var r1 = await asyncBinaryReader.ReadUInt16Async(); + Assert.AreEqual(n1, r1); + var r2 = await asyncBinaryReader.ReadReadUInt64Async(); + Assert.AreEqual(n2, r2); + var r3 = await asyncBinaryReader.ReadUInt32Async(); + Assert.AreEqual(n3, r3); + }); + } + + class FakeSharedArrayPool : ISharedArrayPool + { + public byte[] Rent(int minLength) + { + return new byte[1024]; + } + + public void Return(byte[] array) + { + } + } + } +} diff --git a/tests/dotnetCampus.Ipc.Tests/dotnetCampus.Ipc.Tests.csproj b/tests/dotnetCampus.Ipc.Tests/dotnetCampus.Ipc.Tests.csproj index ccd34cd8..d73f6d3a 100644 --- a/tests/dotnetCampus.Ipc.Tests/dotnetCampus.Ipc.Tests.csproj +++ b/tests/dotnetCampus.Ipc.Tests/dotnetCampus.Ipc.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 @@ -16,8 +16,6 @@ - -