Skip to content

Commit 1379aea

Browse files
author
Jacques Kang
committed
initial implementation
1 parent 45250eb commit 1379aea

File tree

9 files changed

+226
-16
lines changed

9 files changed

+226
-16
lines changed

src/IpcServiceSample.ConsoleClient/MyClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public MyClient(string pipeName)
1313

1414
public Task<MyResponse> GetDataAsync(MyRequest request, bool iAmHandsome)
1515
{
16-
throw new NotImplementedException();
16+
return InvokeAsync<MyResponse>(nameof(GetDataAsync), request, iAmHandsome);
1717
}
1818
}
1919
}

src/IpcServiceSample.ConsoleClient/Program.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ static void Main(string[] args)
1212

1313
private static async Task MainAsync()
1414
{
15-
var client = new MyClient("testpipe");
16-
var request = new MyRequest
15+
Console.WriteLine("Invoking IpcService...");
16+
var client = new MyClient("pipeName");
17+
MyResponse response = await client.GetDataAsync(new MyRequest
1718
{
1819
Message = "Hello"
19-
};
20-
21-
Console.WriteLine("Invoking IpcService...");
22-
MyResponse response = await client.GetDataAsync(request, true);
20+
}, iAmHandsome: true);
2321

2422
Console.WriteLine($"Received response: {response.Message}");
2523
}

src/IpcServiceSample.ConsoleServer/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ static void Main(string[] args)
2525
.AddConsole(LogLevel.Debug);
2626

2727
// TODO start IPC service host
28-
IpcServiceHost
29-
.Buid(serviceProvider as IServiceProvider)
28+
IpcServiceHostBuilder
29+
.Buid("pipeName", serviceProvider as IServiceProvider)
3030
.Start();
3131

3232
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace JKang.IpcServiceFramework
2+
{
3+
internal class IpcRequest
4+
{
5+
public string InterfaceName { get; set; }
6+
public string MethodName { get; set; }
7+
public object[] Parameters { get; set; }
8+
}
9+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace JKang.IpcServiceFramework
5+
{
6+
public class IpcResponse
7+
{
8+
[JsonConstructor]
9+
private IpcResponse(bool succeed, object data, string failure)
10+
{
11+
Succeed = succeed;
12+
Data = data;
13+
Failure = failure;
14+
}
15+
16+
public static IpcResponse Fail(string failure)
17+
{
18+
return new IpcResponse(false, null, failure);
19+
}
20+
21+
public static IpcResponse Success(object data)
22+
{
23+
return new IpcResponse(true, data, null);
24+
}
25+
26+
public bool Succeed { get; }
27+
public object Data { get; }
28+
public string Failure { get; }
29+
}
30+
}
Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,55 @@
1-
namespace JKang.IpcServiceFramework
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using System;
4+
using System.IO;
5+
using System.IO.Pipes;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace JKang.IpcServiceFramework
210
{
3-
public class IpcServiceClient<TInterface>
11+
public abstract class IpcServiceClient<TInterface>
412
{
513
private readonly string _pipeName;
614

715
public IpcServiceClient(string pipeName)
816
{
917
_pipeName = pipeName;
1018
}
19+
20+
public async Task<TResult> InvokeAsync<TResult>(string method, params object[] args)
21+
{
22+
using (var client = new NamedPipeClientStream(".", _pipeName, PipeDirection.InOut, PipeOptions.None))
23+
{
24+
await client.ConnectAsync();
25+
26+
// send request
27+
var request = new IpcRequest
28+
{
29+
InterfaceName = typeof(TInterface).AssemblyQualifiedName,
30+
MethodName = method,
31+
Parameters = args,
32+
};
33+
string json = JsonConvert.SerializeObject(request);
34+
byte[] bytes = Encoding.UTF8.GetBytes(json);
35+
var writer = new BinaryWriter(client);
36+
writer.Write(bytes.Length);
37+
writer.Write(bytes);
38+
await client.FlushAsync();
39+
40+
// receive response
41+
var reader = new StreamReader(client);
42+
string responseJson = await reader.ReadToEndAsync();
43+
IpcResponse response = JsonConvert.DeserializeObject<IpcResponse>(responseJson);
44+
if (response.Succeed)
45+
{
46+
return ((JObject)response.Data).ToObject<TResult>();
47+
}
48+
else
49+
{
50+
throw new InvalidOperationException(response.Failure);
51+
}
52+
}
53+
}
1154
}
1255
}
Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,124 @@
1-
using System;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Linq;
5+
using System;
6+
using System.IO;
7+
using System.IO.Pipes;
8+
using System.Reflection;
9+
using System.Runtime.CompilerServices;
10+
using System.Text;
11+
using System.Threading.Tasks;
212

313
namespace JKang.IpcServiceFramework
414
{
5-
public static class IpcServiceHost
15+
public class IpcServiceHost : IIpcServiceHost
616
{
7-
public static IIpcServiceHost Buid(IServiceProvider serviceProvider)
17+
private readonly string _pipeName;
18+
private readonly IServiceProvider _serviceProvider;
19+
private readonly ILogger<IpcServiceHost> _logger;
20+
21+
public IpcServiceHost(string pipeName, IServiceProvider serviceProvider)
22+
{
23+
_pipeName = pipeName;
24+
_serviceProvider = serviceProvider;
25+
_logger = _serviceProvider.GetService<ILogger<IpcServiceHost>>();
26+
}
27+
28+
public void Start()
29+
{
30+
using (var server = new NamedPipeServerStream(_pipeName, PipeDirection.InOut, 1))
31+
using (var reader = new BinaryReader(server))
32+
using (var writer = new StreamWriter(server))
33+
{
34+
while (true)
35+
{
36+
_logger.LogInformation("IPC service started. Waiting for connection");
37+
server.WaitForConnection();
38+
39+
try
40+
{
41+
_logger.LogDebug("client connected");
42+
43+
int length = reader.ReadInt32();
44+
byte[] bytes = reader.ReadBytes(length);
45+
string json = Encoding.UTF8.GetString(bytes);
46+
IpcRequest request = JsonConvert.DeserializeObject<IpcRequest>(json);
47+
48+
_logger.LogDebug("request received, invoking corresponding method...");
49+
IpcResponse response;
50+
using (IServiceScope scope = _serviceProvider.CreateScope())
51+
{
52+
response = GetReponse(request, scope);
53+
}
54+
55+
_logger.LogDebug("sending response...");
56+
string resultJson = JsonConvert.SerializeObject(response);
57+
writer.Write(resultJson);
58+
writer.Flush();
59+
server.Disconnect();
60+
}
61+
catch (Exception ex)
62+
{
63+
_logger.LogError(ex, ex.Message);
64+
server.Disconnect();
65+
}
66+
}
67+
}
68+
}
69+
70+
private static IpcResponse GetReponse(IpcRequest request, IServiceScope scope)
871
{
9-
throw new NotImplementedException();
72+
var @interface = Type.GetType(request.InterfaceName);
73+
if (@interface == null)
74+
{
75+
return IpcResponse.Fail($"Interface '{@interface}' not found.");
76+
}
77+
78+
object service = scope.ServiceProvider.GetService(@interface);
79+
if (service == null)
80+
{
81+
return IpcResponse.Fail($"No implementation of interface '{@interface}' found.");
82+
}
83+
84+
MethodInfo method = service.GetType().GetMethod(request.MethodName);
85+
if (method == null)
86+
{
87+
return IpcResponse.Fail($"Method '{request.MethodName}' not found in interface '{@interface}'.");
88+
}
89+
90+
ParameterInfo[] paramInfos = method.GetParameters();
91+
if (paramInfos.Length != request.Parameters.Length)
92+
{
93+
return IpcResponse.Fail($"Parameter mismatch.");
94+
}
95+
96+
object[] args = new object[paramInfos.Length];
97+
for (int i = 0; i < args.Length; i++)
98+
{
99+
if (request.Parameters[i].GetType() == paramInfos[i].ParameterType)
100+
{
101+
args[i] = request.Parameters[i];
102+
}
103+
else if (request.Parameters[i] is JObject jObj)
104+
{
105+
args[i] = jObj.ToObject(paramInfos[i].ParameterType);
106+
}
107+
else
108+
{
109+
return IpcResponse.Fail($"Cannot convert value of parameter '{paramInfos[i].Name}'");
110+
}
111+
}
112+
113+
try
114+
{
115+
dynamic @return = (dynamic)method.Invoke(service, args);
116+
return IpcResponse.Success(@return.Result); // TODO: handle non-waitable method
117+
}
118+
catch
119+
{
120+
return IpcResponse.Fail("Internal server error");
121+
}
10122
}
11123
}
12124
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace JKang.IpcServiceFramework
4+
{
5+
public static class IpcServiceHostBuilder
6+
{
7+
public static IIpcServiceHost Buid(string pipeName, IServiceProvider serviceProvider)
8+
{
9+
return new IpcServiceHost(pipeName, serviceProvider);
10+
}
11+
}
12+
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.0</TargetFramework>
4+
<TargetFramework>netstandard2.0</TargetFramework>
55
</PropertyGroup>
66

7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
9+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
10+
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
11+
</ItemGroup>
12+
713
</Project>

0 commit comments

Comments
 (0)