del /AudioWallpaper.1.2.1.nupkg
This commit is contained in:
211
IpcLibrary/Client/IPCClient.cs
Normal file
211
IpcLibrary/Client/IPCClient.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using IpcLibrary.Connection;
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using IpcLibrary.Serialization;
|
||||
using IpcLibrary.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Client {
|
||||
/// <summary>
|
||||
/// IPC客户端实现
|
||||
/// </summary>
|
||||
public class IPCClient : IIPCClient {
|
||||
private readonly NamedPipeConnectionManager _connectionManager;
|
||||
private readonly IServiceRegistry _serviceRegistry;
|
||||
private readonly HeartbeatManager _heartbeatManager;
|
||||
private readonly MessageRouter _messageRouter;
|
||||
private readonly IPCConfiguration _configuration;
|
||||
private readonly string _processId;
|
||||
private bool _disposed;
|
||||
|
||||
public string ProcessId => _processId;
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public event EventHandler<ProcessInfo> ProcessConnected;
|
||||
public event EventHandler<ProcessInfo> ProcessDisconnected;
|
||||
public event EventHandler<IPCMessage> MessageReceived;
|
||||
|
||||
public IPCClient(IPCConfiguration configuration = null) {
|
||||
_configuration = configuration ?? new IPCConfiguration();
|
||||
_processId = $"Client_{Environment.ProcessId}_{Guid.NewGuid():N}";
|
||||
|
||||
var serializer = new CompositeMessageSerializer();
|
||||
_connectionManager = new NamedPipeConnectionManager(serializer, _configuration, "IPCPipe");
|
||||
_serviceRegistry = new ServiceRegistry();
|
||||
_heartbeatManager = new HeartbeatManager();
|
||||
|
||||
var methodCallHandler = new MethodCallHandler(_serviceRegistry);
|
||||
_messageRouter = new MessageRouter(methodCallHandler);
|
||||
|
||||
InitializeEventHandlers();
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync(string serverAddress) {
|
||||
try {
|
||||
var success = await _connectionManager.ConnectToProcessAsync(_processId, serverAddress);
|
||||
if (success) {
|
||||
IsConnected = true;
|
||||
|
||||
if (_configuration.EnableHeartbeat) {
|
||||
_heartbeatManager.StartMonitoring(_processId);
|
||||
_ = Task.Run(SendHeartbeats);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
} catch (Exception ex) {
|
||||
throw new ConnectionException($"连接失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync() {
|
||||
if (!IsConnected)
|
||||
return;
|
||||
|
||||
try {
|
||||
_heartbeatManager.StopMonitoring(_processId);
|
||||
await _connectionManager.DisconnectFromProcessAsync(_processId);
|
||||
IsConnected = false;
|
||||
} catch (Exception ex) {
|
||||
throw new ConnectionException($"断开连接失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> CallMethodAsync<T>(string targetProcessId, string serviceName, string methodName, params object[] parameters) {
|
||||
var result = await CallMethodAsync(targetProcessId, serviceName, methodName, typeof(T), parameters);
|
||||
return result is T typedResult ? typedResult : default(T);
|
||||
}
|
||||
|
||||
public async Task<object> CallMethodAsync(string targetProcessId, string serviceName, string methodName, Type returnType, params object[] parameters) {
|
||||
if (!IsConnected)
|
||||
throw new ConnectionException("客户端未连接");
|
||||
|
||||
try {
|
||||
var request = new MethodCallRequest {
|
||||
ServiceName = serviceName,
|
||||
MethodName = methodName,
|
||||
Parameters = parameters,
|
||||
ParameterTypes = parameters != null ? Array.ConvertAll(parameters, p => p?.GetType()) : new Type[0]
|
||||
};
|
||||
|
||||
var message = new IPCMessage {
|
||||
Type = MessageType.Request,
|
||||
Method = "CallMethod",
|
||||
Parameters = new object[] { request },
|
||||
SourceProcessId = _processId,
|
||||
TargetProcessId = targetProcessId
|
||||
};
|
||||
|
||||
var response = await _messageRouter.SendRequestAsync(
|
||||
message,
|
||||
msg => _connectionManager.SendMessageAsync(targetProcessId, msg),
|
||||
_configuration.MethodCallTimeout);
|
||||
|
||||
if (response.Type == MessageType.Error) {
|
||||
throw new MethodCallException(response.Error);
|
||||
}
|
||||
|
||||
if (response.Result is MethodCallResponse methodResponse) {
|
||||
if (!methodResponse.IsSuccess) {
|
||||
throw new MethodCallException("方法调用失败", methodResponse.Exception);
|
||||
}
|
||||
|
||||
return methodResponse.Result;
|
||||
}
|
||||
|
||||
return response.Result;
|
||||
} catch (Exception ex) when (!(ex is IPCException)) {
|
||||
throw new MethodCallException($"调用方法失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendNotificationAsync(string targetProcessId, string method, params object[] parameters) {
|
||||
if (!IsConnected)
|
||||
throw new ConnectionException("客户端未连接");
|
||||
|
||||
var message = new IPCMessage {
|
||||
Type = MessageType.Notification,
|
||||
Method = method,
|
||||
Parameters = parameters,
|
||||
SourceProcessId = _processId,
|
||||
TargetProcessId = targetProcessId
|
||||
};
|
||||
|
||||
await _connectionManager.SendMessageAsync(targetProcessId, message);
|
||||
}
|
||||
|
||||
public async Task RegisterServiceAsync<T>(string serviceName, T serviceInstance) {
|
||||
await _serviceRegistry.RegisterServiceAsync(serviceName, serviceInstance);
|
||||
}
|
||||
|
||||
public async Task UnregisterServiceAsync(string serviceName) {
|
||||
await _serviceRegistry.UnregisterServiceAsync(serviceName);
|
||||
}
|
||||
|
||||
private void InitializeEventHandlers() {
|
||||
_connectionManager.ProcessConnected += (sender, info) => ProcessConnected?.Invoke(this, info);
|
||||
_connectionManager.ProcessDisconnected += (sender, info) => ProcessDisconnected?.Invoke(this, info);
|
||||
|
||||
_heartbeatManager.ProcessTimeout += async (sender, processId) => {
|
||||
if (processId == _processId) {
|
||||
IsConnected = false;
|
||||
|
||||
if (_configuration.EnableAutoReconnect) {
|
||||
await TryReconnectAsync();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task SendHeartbeats() {
|
||||
while (IsConnected && !_disposed) {
|
||||
try {
|
||||
var heartbeatMessage = new IPCMessage {
|
||||
Type = MessageType.Heartbeat,
|
||||
SourceProcessId = _processId
|
||||
};
|
||||
|
||||
await _connectionManager.BroadcastMessageAsync(heartbeatMessage);
|
||||
await Task.Delay(_configuration.HeartbeatInterval);
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"发送心跳失败: {ex.Message}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryReconnectAsync() {
|
||||
for (int attempt = 0; attempt < _configuration.MaxRetryAttempts; attempt++) {
|
||||
try {
|
||||
await Task.Delay(_configuration.RetryDelay);
|
||||
|
||||
if (await ConnectAsync("default")) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"重连尝试 {attempt + 1} 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
|
||||
try {
|
||||
DisconnectAsync().Wait(TimeSpan.FromSeconds(5));
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"释放资源时发生错误: {ex.Message}");
|
||||
}
|
||||
|
||||
_connectionManager?.Dispose();
|
||||
_heartbeatManager?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
65
IpcLibrary/Client/IPCClientExtensions.cs
Normal file
65
IpcLibrary/Client/IPCClientExtensions.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Client {
|
||||
/// <summary>
|
||||
/// 客户端扩展方法
|
||||
/// </summary>
|
||||
public static class IPCClientExtensions {
|
||||
/// <summary>
|
||||
/// 调用无返回值的方法
|
||||
/// </summary>
|
||||
public static async Task CallMethodAsync(this IIPCClient client, string targetProcessId, string serviceName, string methodName, params object[] parameters) {
|
||||
await client.CallMethodAsync<object>(targetProcessId, serviceName, methodName, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册多个服务
|
||||
/// </summary>
|
||||
public static async Task RegisterServicesAsync(this IIPCClient client, Dictionary<string, object> services) {
|
||||
foreach (var kvp in services) {
|
||||
await client.RegisterServiceAsync(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量发送通知
|
||||
/// </summary>
|
||||
public static async Task SendNotificationsAsync(this IIPCClient client, IEnumerable<string> targetProcessIds, string method, params object[] parameters) {
|
||||
var tasks = new List<Task>();
|
||||
|
||||
foreach (var processId in targetProcessIds) {
|
||||
tasks.Add(client.SendNotificationAsync(processId, method, parameters));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用重试机制调用方法
|
||||
/// </summary>
|
||||
public static async Task<T> CallMethodWithRetryAsync<T>(this IIPCClient client, string targetProcessId, string serviceName, string methodName, int maxRetries = 3, TimeSpan? retryDelay = null, params object[] parameters) {
|
||||
var delay = retryDelay ?? TimeSpan.FromSeconds(1);
|
||||
Exception lastException = null;
|
||||
|
||||
for (int attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await client.CallMethodAsync<T>(targetProcessId, serviceName, methodName, parameters);
|
||||
} catch (Exception ex) {
|
||||
lastException = ex;
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new MethodCallException($"方法调用在 {maxRetries + 1} 次尝试后仍然失败", lastException);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
IpcLibrary/Client/IPCClientFactory.cs
Normal file
42
IpcLibrary/Client/IPCClientFactory.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Client {
|
||||
/// <summary>
|
||||
/// IPC客户端工厂
|
||||
/// </summary>
|
||||
public static class IPCClientFactory {
|
||||
/// <summary>
|
||||
/// 创建默认配置的客户端
|
||||
/// </summary>
|
||||
public static IIPCClient CreateClient() {
|
||||
return new IPCClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建自定义配置的客户端
|
||||
/// </summary>
|
||||
public static IIPCClient CreateClient(IPCConfiguration configuration) {
|
||||
return new IPCClient(configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并连接客户端
|
||||
/// </summary>
|
||||
public static async Task<IIPCClient> CreateAndConnectAsync(string serverAddress, IPCConfiguration configuration = null) {
|
||||
var client = new IPCClient(configuration);
|
||||
|
||||
if (!await client.ConnectAsync(serverAddress)) {
|
||||
client.Dispose();
|
||||
throw new ConnectionException("无法连接到服务器");
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
198
IpcLibrary/Connection/NamedPipeConnectionManager.cs
Normal file
198
IpcLibrary/Connection/NamedPipeConnectionManager.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Connection {
|
||||
/// <summary>
|
||||
/// 基于命名管道的连接管理器
|
||||
/// </summary>
|
||||
public class NamedPipeConnectionManager : IConnectionManager, IDisposable {
|
||||
private readonly ConcurrentDictionary<string, ProcessConnection> _connections;
|
||||
private readonly IMessageSerializer _serializer;
|
||||
private readonly IPCConfiguration _configuration;
|
||||
private readonly string _serverPipeName;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||
private NamedPipeServerStream _serverPipe;
|
||||
private bool _isServer;
|
||||
private bool _disposed;
|
||||
|
||||
public event EventHandler<ProcessInfo> ProcessConnected;
|
||||
public event EventHandler<ProcessInfo> ProcessDisconnected;
|
||||
|
||||
public NamedPipeConnectionManager(IMessageSerializer serializer, IPCConfiguration configuration, string pipeName) {
|
||||
_connections = new ConcurrentDictionary<string, ProcessConnection>();
|
||||
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_serverPipeName = pipeName ?? throw new ArgumentNullException(nameof(pipeName));
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
public async Task StartServerAsync() {
|
||||
if (_isServer)
|
||||
return;
|
||||
|
||||
_isServer = true;
|
||||
Console.WriteLine("123456");
|
||||
await Task.Run(() => ListenForConnections(_cancellationTokenSource.Token));
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectToProcessAsync(string processId, string address) {
|
||||
try {
|
||||
if (_connections.ContainsKey(processId))
|
||||
return true;
|
||||
|
||||
var clientPipe = new NamedPipeClientStream(".", _serverPipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
|
||||
Console.WriteLine("00000000000");
|
||||
await clientPipe.ConnectAsync(_configuration.ConnectionTimeout, _cancellationTokenSource.Token);
|
||||
Console.WriteLine("1111111111");
|
||||
var connection = new ProcessConnection(processId, clientPipe, _serializer);
|
||||
connection.MessageReceived += OnConnectionMessageReceived;
|
||||
connection.Disconnected += OnConnectionDisconnected;
|
||||
|
||||
if (_connections.TryAdd(processId, connection)) {
|
||||
Console.WriteLine("00000000000");
|
||||
await connection.StartAsync(_cancellationTokenSource.Token);
|
||||
Console.WriteLine("000001000000");
|
||||
var processInfo = new ProcessInfo {
|
||||
ProcessId = processId,
|
||||
Status = ProcessStatus.Connected,
|
||||
LastHeartbeat = DateTime.UtcNow
|
||||
};
|
||||
|
||||
ProcessConnected?.Invoke(this, processInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception ex) {
|
||||
throw new ConnectionException($"连接到进程 {processId} 失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectFromProcessAsync(string processId) {
|
||||
if (_connections.TryRemove(processId, out var connection)) {
|
||||
await connection.DisconnectAsync();
|
||||
|
||||
var processInfo = new ProcessInfo {
|
||||
ProcessId = processId,
|
||||
Status = ProcessStatus.Disconnected,
|
||||
LastHeartbeat = DateTime.UtcNow
|
||||
};
|
||||
|
||||
ProcessDisconnected?.Invoke(this, processInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SendMessageAsync(string targetProcessId, IPCMessage message) {
|
||||
if (_connections.TryGetValue(targetProcessId, out var connection)) {
|
||||
return await connection.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task BroadcastMessageAsync(IPCMessage message) {
|
||||
var tasks = _connections.Values.Select(conn => conn.SendMessageAsync(message));
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
public ProcessInfo GetProcessInfo(string processId) {
|
||||
if (_connections.TryGetValue(processId, out var connection)) {
|
||||
return connection.GetProcessInfo();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ProcessInfo> GetAllProcesses() {
|
||||
return _connections.Values.Select(conn => conn.GetProcessInfo()).ToList();
|
||||
}
|
||||
|
||||
private async Task ListenForConnections(CancellationToken cancellationToken) {
|
||||
while (!cancellationToken.IsCancellationRequested) {
|
||||
try {
|
||||
_serverPipe = new NamedPipeServerStream(_serverPipeName, PipeDirection.InOut,
|
||||
_configuration.MaxConcurrentConnections, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||
await _serverPipe.WaitForConnectionAsync(cancellationToken);
|
||||
var processId = Guid.NewGuid().ToString();
|
||||
var connection = new ProcessConnection(processId, _serverPipe, _serializer);
|
||||
connection.MessageReceived += OnConnectionMessageReceived;
|
||||
connection.Disconnected += OnConnectionDisconnected;
|
||||
|
||||
if (_connections.TryAdd(processId, connection)) {
|
||||
Console.WriteLine(123);
|
||||
await connection.StartAsync(cancellationToken);
|
||||
Console.WriteLine(254);
|
||||
var processInfo = new ProcessInfo {
|
||||
ProcessId = processId,
|
||||
Status = ProcessStatus.Connected,
|
||||
LastHeartbeat = DateTime.UtcNow
|
||||
};
|
||||
|
||||
ProcessConnected?.Invoke(this, processInfo);
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
break;
|
||||
} catch (Exception ex) {
|
||||
// 记录错误,继续监听
|
||||
Console.WriteLine($"监听连接时发生错误: {ex.Message}");
|
||||
await Task.Delay(1000, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnectionMessageReceived(object sender, IPCMessage message) {
|
||||
// 消息将由上层处理
|
||||
}
|
||||
|
||||
private async void OnConnectionDisconnected(object sender, EventArgs e) {
|
||||
if (sender is ProcessConnection connection) {
|
||||
var processId = connection.ProcessId;
|
||||
_connections.TryRemove(processId, out _);
|
||||
|
||||
var processInfo = new ProcessInfo {
|
||||
ProcessId = processId,
|
||||
Status = ProcessStatus.Disconnected,
|
||||
LastHeartbeat = DateTime.UtcNow
|
||||
};
|
||||
|
||||
ProcessDisconnected?.Invoke(this, processInfo);
|
||||
|
||||
// 如果启用自动重连
|
||||
if (_configuration.EnableAutoReconnect && !_disposed) {
|
||||
await TryReconnectAsync(processId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryReconnectAsync(string processId) {
|
||||
for (int attempt = 0; attempt < _configuration.MaxRetryAttempts; attempt++) {
|
||||
try {
|
||||
await Task.Delay(_configuration.RetryDelay);
|
||||
|
||||
if (await ConnectToProcessAsync(processId, _serverPipeName)) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"重连尝试 {attempt + 1} 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
133
IpcLibrary/Connection/ProcessConnection.cs
Normal file
133
IpcLibrary/Connection/ProcessConnection.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using IpcLibrary.Core;
|
||||
using System.IO.Pipes;
|
||||
|
||||
namespace IpcLibrary.Connection {
|
||||
/// <summary>
|
||||
/// 单个进程连接
|
||||
/// </summary>
|
||||
internal class ProcessConnection : IDisposable {
|
||||
private readonly string _processId;
|
||||
private readonly PipeStream _pipe;
|
||||
private readonly IMessageSerializer _serializer;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly SemaphoreSlim _sendSemaphore;
|
||||
private bool _disposed;
|
||||
|
||||
public string ProcessId => _processId;
|
||||
public bool IsConnected => _pipe?.IsConnected == true;
|
||||
|
||||
public event EventHandler<IPCMessage> MessageReceived;
|
||||
public event EventHandler Disconnected;
|
||||
|
||||
public ProcessConnection(string processId, PipeStream pipe, IMessageSerializer serializer) {
|
||||
_processId = processId ?? throw new ArgumentNullException(nameof(processId));
|
||||
_pipe = pipe ?? throw new ArgumentNullException(nameof(pipe));
|
||||
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_sendSemaphore = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken) {
|
||||
var combinedToken = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
cancellationToken, _cancellationTokenSource.Token).Token;
|
||||
|
||||
Task.Run(() => ReceiveMessages(combinedToken), combinedToken);
|
||||
}
|
||||
|
||||
public async Task<bool> SendMessageAsync(IPCMessage message) {
|
||||
if (!IsConnected || _disposed)
|
||||
return false;
|
||||
|
||||
await _sendSemaphore.WaitAsync();
|
||||
try {
|
||||
var data = _serializer.Serialize(message);
|
||||
var lengthBytes = BitConverter.GetBytes(data.Length);
|
||||
|
||||
await _pipe.WriteAsync(lengthBytes, 0, lengthBytes.Length);
|
||||
await _pipe.WriteAsync(data, 0, data.Length);
|
||||
await _pipe.FlushAsync();
|
||||
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"发送消息失败: {ex.Message}");
|
||||
return false;
|
||||
} finally {
|
||||
_sendSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync() {
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
try {
|
||||
_pipe?.Close();
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"关闭管道时发生错误: {ex.Message}");
|
||||
}
|
||||
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public ProcessInfo GetProcessInfo() {
|
||||
return new ProcessInfo {
|
||||
ProcessId = _processId,
|
||||
Status = IsConnected ? ProcessStatus.Connected : ProcessStatus.Disconnected,
|
||||
LastHeartbeat = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ReceiveMessages(CancellationToken cancellationToken) {
|
||||
var lengthBuffer = new byte[4];
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested && IsConnected) {
|
||||
try {
|
||||
// 读取消息长度
|
||||
var bytesRead = await _pipe.ReadAsync(lengthBuffer, 0, 4, cancellationToken);
|
||||
if (bytesRead != 4)
|
||||
break;
|
||||
|
||||
var messageLength = BitConverter.ToInt32(lengthBuffer, 0);
|
||||
if (messageLength <= 0 || messageLength > 1024 * 1024) // 限制最大消息大小
|
||||
break;
|
||||
|
||||
// 读取消息内容
|
||||
var messageBuffer = new byte[messageLength];
|
||||
var totalBytesRead = 0;
|
||||
|
||||
while (totalBytesRead < messageLength && !cancellationToken.IsCancellationRequested) {
|
||||
var bytes = await _pipe.ReadAsync(messageBuffer, totalBytesRead,
|
||||
messageLength - totalBytesRead, cancellationToken);
|
||||
|
||||
if (bytes == 0)
|
||||
break;
|
||||
|
||||
totalBytesRead += bytes;
|
||||
}
|
||||
|
||||
if (totalBytesRead == messageLength) {
|
||||
var message = _serializer.Deserialize<IPCMessage>(messageBuffer);
|
||||
MessageReceived?.Invoke(this, message);
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
break;
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"接收消息时发生错误: {ex.Message}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await DisconnectAsync();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed) return;
|
||||
|
||||
_disposed = true;
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
36
IpcLibrary/Core/Exceptions/IPCException.cs
Normal file
36
IpcLibrary/Core/Exceptions/IPCException.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core.Exceptions {
|
||||
/// <summary>
|
||||
/// IPC异常基类
|
||||
/// </summary>
|
||||
public class IPCException : Exception {
|
||||
public IPCException(string message) : base(message) { }
|
||||
public IPCException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
/// <summary>
|
||||
/// 连接异常
|
||||
/// </summary>
|
||||
public class ConnectionException : IPCException {
|
||||
public ConnectionException(string message) : base(message) { }
|
||||
public ConnectionException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
/// <summary>
|
||||
/// 方法调用异常
|
||||
/// </summary>
|
||||
public class MethodCallException : IPCException {
|
||||
public MethodCallException(string message) : base(message) { }
|
||||
public MethodCallException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
/// <summary>
|
||||
/// 超时异常
|
||||
/// </summary>
|
||||
public class TimeoutException : IPCException {
|
||||
public TimeoutException(string message) : base(message) { }
|
||||
public TimeoutException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
22
IpcLibrary/Core/IConnectionManager.cs
Normal file
22
IpcLibrary/Core/IConnectionManager.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// 连接管理器接口
|
||||
/// </summary>
|
||||
public interface IConnectionManager {
|
||||
event EventHandler<ProcessInfo> ProcessConnected;
|
||||
event EventHandler<ProcessInfo> ProcessDisconnected;
|
||||
|
||||
Task<bool> ConnectToProcessAsync(string processId, string address);
|
||||
Task DisconnectFromProcessAsync(string processId);
|
||||
Task<bool> SendMessageAsync(string targetProcessId, IPCMessage message);
|
||||
Task BroadcastMessageAsync(IPCMessage message);
|
||||
ProcessInfo GetProcessInfo(string processId);
|
||||
IReadOnlyList<ProcessInfo> GetAllProcesses();
|
||||
}
|
||||
}
|
||||
20
IpcLibrary/Core/IHeartbeatManager.cs
Normal file
20
IpcLibrary/Core/IHeartbeatManager.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// 心跳管理器接口
|
||||
/// </summary>
|
||||
public interface IHeartbeatManager {
|
||||
event EventHandler<string> ProcessTimeout;
|
||||
|
||||
void StartMonitoring(string processId);
|
||||
void StopMonitoring(string processId);
|
||||
void UpdateHeartbeat(string processId);
|
||||
void SetHeartbeatInterval(TimeSpan interval);
|
||||
void SetTimeoutThreshold(TimeSpan timeout);
|
||||
}
|
||||
}
|
||||
27
IpcLibrary/Core/IIPCClient.cs
Normal file
27
IpcLibrary/Core/IIPCClient.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// IPC客户端接口
|
||||
/// </summary>
|
||||
public interface IIPCClient : IDisposable {
|
||||
string ProcessId { get; }
|
||||
bool IsConnected { get; }
|
||||
|
||||
event EventHandler<ProcessInfo> ProcessConnected;
|
||||
event EventHandler<ProcessInfo> ProcessDisconnected;
|
||||
event EventHandler<IPCMessage> MessageReceived;
|
||||
|
||||
Task<bool> ConnectAsync(string serverAddress);
|
||||
Task DisconnectAsync();
|
||||
Task<T> CallMethodAsync<T>(string targetProcessId, string serviceName, string methodName, params object[] parameters);
|
||||
Task<object> CallMethodAsync(string targetProcessId, string serviceName, string methodName, Type returnType, params object[] parameters);
|
||||
Task SendNotificationAsync(string targetProcessId, string method, params object[] parameters);
|
||||
Task RegisterServiceAsync<T>(string serviceName, T serviceInstance);
|
||||
Task UnregisterServiceAsync(string serviceName);
|
||||
}
|
||||
}
|
||||
27
IpcLibrary/Core/IIPCServer.cs
Normal file
27
IpcLibrary/Core/IIPCServer.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// IPC服务器接口
|
||||
/// </summary>
|
||||
public interface IIPCServer : IDisposable {
|
||||
bool IsRunning { get; }
|
||||
IReadOnlyList<ProcessInfo> ConnectedProcesses { get; }
|
||||
|
||||
event EventHandler<ProcessInfo> ProcessConnected;
|
||||
event EventHandler<ProcessInfo> ProcessDisconnected;
|
||||
event EventHandler<IPCMessage> MessageReceived;
|
||||
|
||||
Task StartAsync(string address);
|
||||
Task StopAsync();
|
||||
Task<T> CallMethodAsync<T>(string targetProcessId, string serviceName, string methodName, params object[] parameters);
|
||||
Task SendNotificationAsync(string targetProcessId, string method, params object[] parameters);
|
||||
Task BroadcastNotificationAsync(string method, params object[] parameters);
|
||||
Task RegisterServiceAsync<T>(string serviceName, T serviceInstance);
|
||||
Task UnregisterServiceAsync(string serviceName);
|
||||
}
|
||||
}
|
||||
16
IpcLibrary/Core/IMessageSerializer.cs
Normal file
16
IpcLibrary/Core/IMessageSerializer.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// 序列化器接口
|
||||
/// </summary>
|
||||
public interface IMessageSerializer {
|
||||
byte[] Serialize(object obj);
|
||||
T Deserialize<T>(byte[] data);
|
||||
object Deserialize(byte[] data, Type type);
|
||||
}
|
||||
}
|
||||
22
IpcLibrary/Core/IPCConfiguration.cs
Normal file
22
IpcLibrary/Core/IPCConfiguration.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// 配置选项
|
||||
/// </summary>
|
||||
public class IPCConfiguration {
|
||||
public TimeSpan HeartbeatInterval { get; set; } = TimeSpan.FromSeconds(5);
|
||||
public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
public TimeSpan MethodCallTimeout { get; set; } = TimeSpan.FromSeconds(60);
|
||||
public int MaxRetryAttempts { get; set; } = 3;
|
||||
public TimeSpan RetryDelay { get; set; } = TimeSpan.FromSeconds(1);
|
||||
public bool EnableAutoReconnect { get; set; } = true;
|
||||
public bool EnableHeartbeat { get; set; } = true;
|
||||
public int MaxConcurrentConnections { get; set; } = 100;
|
||||
public string LogLevel { get; set; } = "Info";
|
||||
}
|
||||
}
|
||||
23
IpcLibrary/Core/IPCMessage.cs
Normal file
23
IpcLibrary/Core/IPCMessage.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// IPC消息基类
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class IPCMessage {
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
public MessageType Type { get; set; }
|
||||
public string Method { get; set; }
|
||||
public object[] Parameters { get; set; }
|
||||
public object Result { get; set; }
|
||||
public string Error { get; set; }
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
public string SourceProcessId { get; set; }
|
||||
public string TargetProcessId { get; set; }
|
||||
}
|
||||
}
|
||||
19
IpcLibrary/Core/IServiceRegistry.cs
Normal file
19
IpcLibrary/Core/IServiceRegistry.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// 服务注册器接口
|
||||
/// </summary>
|
||||
public interface IServiceRegistry {
|
||||
Task RegisterServiceAsync<T>(string serviceName, T serviceInstance);
|
||||
Task UnregisterServiceAsync(string serviceName);
|
||||
object GetService(string serviceName);
|
||||
T GetService<T>(string serviceName);
|
||||
bool IsServiceRegistered(string serviceName);
|
||||
IReadOnlyList<string> GetRegisteredServices();
|
||||
}
|
||||
}
|
||||
18
IpcLibrary/Core/MessageType.cs
Normal file
18
IpcLibrary/Core/MessageType.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// IPC消息类型
|
||||
/// </summary>
|
||||
public enum MessageType {
|
||||
Request,
|
||||
Response,
|
||||
Notification,
|
||||
Heartbeat,
|
||||
Error
|
||||
}
|
||||
}
|
||||
18
IpcLibrary/Core/MethodCallRequest.cs
Normal file
18
IpcLibrary/Core/MethodCallRequest.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// 方法调用请求
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MethodCallRequest {
|
||||
public string ServiceName { get; set; }
|
||||
public string MethodName { get; set; }
|
||||
public Type[] ParameterTypes { get; set; }
|
||||
public object[] Parameters { get; set; }
|
||||
}
|
||||
}
|
||||
18
IpcLibrary/Core/MethodCallResponse.cs
Normal file
18
IpcLibrary/Core/MethodCallResponse.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
// <summary>
|
||||
/// 方法调用响应
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MethodCallResponse {
|
||||
public object Result { get; set; }
|
||||
public Exception Exception { get; set; }
|
||||
public bool IsSuccess => Exception == null;
|
||||
public Type ResultType { get; set; }
|
||||
}
|
||||
}
|
||||
18
IpcLibrary/Core/ProcessInfo.cs
Normal file
18
IpcLibrary/Core/ProcessInfo.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// 进程信息
|
||||
/// </summary>
|
||||
public class ProcessInfo {
|
||||
public string ProcessId { get; set; }
|
||||
public string ProcessName { get; set; }
|
||||
public ProcessStatus Status { get; set; }
|
||||
public DateTime LastHeartbeat { get; set; }
|
||||
public Dictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
18
IpcLibrary/Core/ProcessStatus.cs
Normal file
18
IpcLibrary/Core/ProcessStatus.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Core {
|
||||
/// <summary>
|
||||
/// 进程状态
|
||||
/// </summary>
|
||||
public enum ProcessStatus {
|
||||
Unknown,
|
||||
Connected,
|
||||
Disconnected,
|
||||
Reconnecting,
|
||||
Failed
|
||||
}
|
||||
}
|
||||
9
IpcLibrary/IpcLibrary.csproj
Normal file
9
IpcLibrary/IpcLibrary.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
52
IpcLibrary/Serialization/BinaryMessageSerializer.cs
Normal file
52
IpcLibrary/Serialization/BinaryMessageSerializer.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Serialization {
|
||||
/// <summary>
|
||||
/// 二进制序列化器实现(用于复杂对象)
|
||||
/// </summary>
|
||||
public class BinaryMessageSerializer : IMessageSerializer {
|
||||
public byte[] Serialize(object obj) {
|
||||
try {
|
||||
using var stream = new MemoryStream();
|
||||
#pragma warning disable SYSLIB0011 // 类型或成员已过时
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning restore SYSLIB0011 // 类型或成员已过时
|
||||
formatter.Serialize(stream, obj);
|
||||
return stream.ToArray();
|
||||
} catch (Exception ex) {
|
||||
throw new IPCException($"二进制序列化失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public T Deserialize<T>(byte[] data) {
|
||||
try {
|
||||
using var stream = new MemoryStream(data);
|
||||
#pragma warning disable SYSLIB0011 // 类型或成员已过时
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning restore SYSLIB0011 // 类型或成员已过时
|
||||
return (T)formatter.Deserialize(stream);
|
||||
} catch (Exception ex) {
|
||||
throw new IPCException($"二进制反序列化失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public object Deserialize(byte[] data, Type type) {
|
||||
try {
|
||||
using var stream = new MemoryStream(data);
|
||||
#pragma warning disable SYSLIB0011 // 类型或成员已过时
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning restore SYSLIB0011 // 类型或成员已过时
|
||||
return formatter.Deserialize(stream);
|
||||
} catch (Exception ex) {
|
||||
throw new IPCException($"二进制反序列化失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
IpcLibrary/Serialization/CompositeMessageSerializer.cs
Normal file
93
IpcLibrary/Serialization/CompositeMessageSerializer.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using IPCLibrary.Serialization;
|
||||
|
||||
namespace IpcLibrary.Serialization {
|
||||
/// <summary>
|
||||
/// 复合序列化器 - 根据数据类型选择最适合的序列化方式
|
||||
/// </summary>
|
||||
public class CompositeMessageSerializer : IMessageSerializer {
|
||||
private readonly JsonMessageSerializer _jsonSerializer;
|
||||
private readonly BinaryMessageSerializer _binarySerializer;
|
||||
|
||||
public CompositeMessageSerializer() {
|
||||
_jsonSerializer = new JsonMessageSerializer();
|
||||
_binarySerializer = new BinaryMessageSerializer();
|
||||
}
|
||||
|
||||
public byte[] Serialize(object obj) {
|
||||
if (obj == null)
|
||||
return new byte[0];
|
||||
|
||||
// 对于简单类型和已知类型使用JSON,复杂类型使用二进制
|
||||
if (IsJsonSerializable(obj.GetType())) {
|
||||
var jsonData = _jsonSerializer.Serialize(obj);
|
||||
var result = new byte[jsonData.Length + 1];
|
||||
result[0] = 1; // JSON标识
|
||||
Array.Copy(jsonData, 0, result, 1, jsonData.Length);
|
||||
return result;
|
||||
} else {
|
||||
var binaryData = _binarySerializer.Serialize(obj);
|
||||
var result = new byte[binaryData.Length + 1];
|
||||
result[0] = 2; // Binary标识
|
||||
Array.Copy(binaryData, 0, result, 1, binaryData.Length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public T Deserialize<T>(byte[] data) {
|
||||
if (data.Length == 0)
|
||||
return default(T);
|
||||
|
||||
var serializationType = data[0];
|
||||
var actualData = new byte[data.Length - 1];
|
||||
Array.Copy(data, 1, actualData, 0, actualData.Length);
|
||||
|
||||
switch (serializationType) {
|
||||
case 1: // JSON
|
||||
return _jsonSerializer.Deserialize<T>(actualData);
|
||||
case 2: // Binary
|
||||
return _binarySerializer.Deserialize<T>(actualData);
|
||||
default:
|
||||
throw new IPCException($"未知的序列化类型: {serializationType}");
|
||||
}
|
||||
}
|
||||
|
||||
public object Deserialize(byte[] data, Type type) {
|
||||
if (data.Length == 0)
|
||||
return null;
|
||||
|
||||
var serializationType = data[0];
|
||||
var actualData = new byte[data.Length - 1];
|
||||
Array.Copy(data, 1, actualData, 0, actualData.Length);
|
||||
|
||||
switch (serializationType) {
|
||||
case 1: // JSON
|
||||
return _jsonSerializer.Deserialize(actualData, type);
|
||||
case 2: // Binary
|
||||
return _binarySerializer.Deserialize(actualData, type);
|
||||
default:
|
||||
throw new IPCException($"未知的序列化类型: {serializationType}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsJsonSerializable(Type type) {
|
||||
// 基本类型
|
||||
if (type.IsPrimitive || type == typeof(string) || type == typeof(DateTime) || type == typeof(Guid))
|
||||
return true;
|
||||
|
||||
// 数组
|
||||
if (type.IsArray && IsJsonSerializable(type.GetElementType()))
|
||||
return true;
|
||||
|
||||
// 已知的IPC消息类型
|
||||
if (typeof(IPCMessage).IsAssignableFrom(type) ||
|
||||
typeof(MethodCallRequest).IsAssignableFrom(type) ||
|
||||
typeof(MethodCallResponse).IsAssignableFrom(type))
|
||||
return true;
|
||||
|
||||
// 其他情况使用二进制序列化
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
IpcLibrary/Serialization/ExceptionConverter.cs
Normal file
43
IpcLibrary/Serialization/ExceptionConverter.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Serialization {
|
||||
/// <summary>
|
||||
/// Exception异常的JSON转换器
|
||||
/// </summary>
|
||||
public class ExceptionConverter : JsonConverter<Exception> {
|
||||
public override Exception Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||
using var doc = JsonDocument.ParseValue(ref reader);
|
||||
var root = doc.RootElement;
|
||||
|
||||
var message = root.TryGetProperty("message", out var msgElement) ? msgElement.GetString() : "Unknown error";
|
||||
var typeName = root.TryGetProperty("type", out var typeElement) ? typeElement.GetString() : null;
|
||||
|
||||
if (!string.IsNullOrEmpty(typeName)) {
|
||||
var exceptionType = Type.GetType(typeName);
|
||||
if (exceptionType != null && typeof(Exception).IsAssignableFrom(exceptionType)) {
|
||||
try {
|
||||
return (Exception)Activator.CreateInstance(exceptionType, message);
|
||||
} catch {
|
||||
// 如果创建特定异常类型失败,返回通用异常
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Exception(message);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Exception value, JsonSerializerOptions options) {
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("type", value.GetType().AssemblyQualifiedName);
|
||||
writer.WriteString("message", value.Message);
|
||||
writer.WriteString("stackTrace", value.StackTrace);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
66
IpcLibrary/Serialization/JsonMessageSerializer.cs
Normal file
66
IpcLibrary/Serialization/JsonMessageSerializer.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using IpcLibrary.Serialization;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace IPCLibrary.Serialization {
|
||||
/// <summary>
|
||||
/// JSON序列化器实现
|
||||
/// </summary>
|
||||
public class JsonMessageSerializer : IMessageSerializer {
|
||||
private readonly JsonSerializerOptions _options;
|
||||
|
||||
public JsonMessageSerializer() {
|
||||
_options = new JsonSerializerOptions {
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter(),
|
||||
new TypeConverter(),
|
||||
new ExceptionConverter()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public byte[] Serialize(object obj) {
|
||||
try {
|
||||
var json = JsonSerializer.Serialize(obj, obj.GetType(), _options);
|
||||
return System.Text.Encoding.UTF8.GetBytes(json);
|
||||
} catch (Exception ex) {
|
||||
throw new IPCException($"序列化失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public T Deserialize<T>(byte[] data) {
|
||||
try {
|
||||
var json = System.Text.Encoding.UTF8.GetString(data);
|
||||
return JsonSerializer.Deserialize<T>(json, _options);
|
||||
} catch (Exception ex) {
|
||||
throw new IPCException($"反序列化失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public object Deserialize(byte[] data, Type type) {
|
||||
try {
|
||||
var json = System.Text.Encoding.UTF8.GetString(data);
|
||||
return JsonSerializer.Deserialize(json, type, _options);
|
||||
} catch (Exception ex) {
|
||||
throw new IPCException($"反序列化失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
23
IpcLibrary/Serialization/TypeConverter.cs
Normal file
23
IpcLibrary/Serialization/TypeConverter.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Serialization {
|
||||
/// <summary>
|
||||
/// Type类型的JSON转换器
|
||||
/// </summary>
|
||||
public class TypeConverter : JsonConverter<Type> {
|
||||
public override Type Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||
var typeName = reader.GetString();
|
||||
return string.IsNullOrEmpty(typeName) ? null : Type.GetType(typeName);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options) {
|
||||
writer.WriteStringValue(value?.AssemblyQualifiedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
258
IpcLibrary/Server/IPCServer.cs
Normal file
258
IpcLibrary/Server/IPCServer.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using IpcLibrary.Connection;
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using IpcLibrary.Serialization;
|
||||
using IpcLibrary.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Server {
|
||||
/// <summary>
|
||||
/// IPC服务器实现
|
||||
/// </summary>
|
||||
public class IPCServer : IIPCServer {
|
||||
private readonly NamedPipeConnectionManager _connectionManager;
|
||||
private readonly IServiceRegistry _serviceRegistry;
|
||||
private readonly HeartbeatManager _heartbeatManager;
|
||||
private readonly MessageRouter _messageRouter;
|
||||
private readonly IPCConfiguration _configuration;
|
||||
private readonly string _serverId;
|
||||
private bool _disposed;
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
public IReadOnlyList<ProcessInfo> ConnectedProcesses => _connectionManager.GetAllProcesses();
|
||||
|
||||
public DateTime StartTime { get; internal set; }
|
||||
|
||||
public event EventHandler<ProcessInfo> ProcessConnected;
|
||||
public event EventHandler<ProcessInfo> ProcessDisconnected;
|
||||
public event EventHandler<IPCMessage> MessageReceived;
|
||||
|
||||
public IPCServer(IPCConfiguration configuration = null) {
|
||||
_configuration = configuration ?? new IPCConfiguration();
|
||||
_serverId = $"Server_{Environment.ProcessId}_{Guid.NewGuid():N}";
|
||||
|
||||
var serializer = new CompositeMessageSerializer();
|
||||
_connectionManager = new NamedPipeConnectionManager(serializer, _configuration, "IPCPipe");
|
||||
_serviceRegistry = new ServiceRegistry();
|
||||
_heartbeatManager = new HeartbeatManager();
|
||||
|
||||
var methodCallHandler = new MethodCallHandler(_serviceRegistry);
|
||||
_messageRouter = new MessageRouter(methodCallHandler);
|
||||
|
||||
InitializeEventHandlers();
|
||||
StartTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public async Task StartAsync(string address) {
|
||||
if (IsRunning)
|
||||
return;
|
||||
|
||||
try {
|
||||
Console.WriteLine($"IPC服务器已启动,地址: {address}");
|
||||
if (_connectionManager is NamedPipeConnectionManager namedPipeManager) {
|
||||
await namedPipeManager.StartServerAsync();
|
||||
}
|
||||
IsRunning = true;
|
||||
|
||||
if (_configuration.EnableHeartbeat) {
|
||||
_heartbeatManager.SetHeartbeatInterval(_configuration.HeartbeatInterval);
|
||||
_heartbeatManager.SetTimeoutThreshold(_configuration.ConnectionTimeout);
|
||||
}
|
||||
|
||||
Console.WriteLine($"IPC服务器已启动,地址: {address}");
|
||||
} catch (Exception ex) {
|
||||
throw new ConnectionException($"启动服务器失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync() {
|
||||
if (!IsRunning)
|
||||
return;
|
||||
|
||||
try {
|
||||
// 通知所有连接的进程服务器即将关闭
|
||||
var shutdownMessage = new IPCMessage {
|
||||
Type = MessageType.Notification,
|
||||
Method = "ServerShutdown",
|
||||
SourceProcessId = _serverId
|
||||
};
|
||||
|
||||
await _connectionManager.BroadcastMessageAsync(shutdownMessage);
|
||||
|
||||
// 断开所有连接
|
||||
var processes = ConnectedProcesses.ToList();
|
||||
foreach (var process in processes) {
|
||||
await _connectionManager.DisconnectFromProcessAsync(process.ProcessId);
|
||||
}
|
||||
|
||||
IsRunning = false;
|
||||
Console.WriteLine("IPC服务器已停止");
|
||||
} catch (Exception ex) {
|
||||
throw new ConnectionException($"停止服务器失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> CallMethodAsync<T>(string targetProcessId, string serviceName, string methodName, params object[] parameters) {
|
||||
var result = await CallMethodAsync(targetProcessId, serviceName, methodName, typeof(T), parameters);
|
||||
return result is T typedResult ? typedResult : default(T);
|
||||
}
|
||||
|
||||
public async Task<object> CallMethodAsync(string targetProcessId, string serviceName, string methodName, Type returnType, params object[] parameters) {
|
||||
if (!IsRunning)
|
||||
throw new InvalidOperationException("服务器未运行");
|
||||
|
||||
try {
|
||||
var request = new MethodCallRequest {
|
||||
ServiceName = serviceName,
|
||||
MethodName = methodName,
|
||||
Parameters = parameters,
|
||||
ParameterTypes = parameters != null ? Array.ConvertAll(parameters, p => p?.GetType()) : new Type[0]
|
||||
};
|
||||
|
||||
var message = new IPCMessage {
|
||||
Type = MessageType.Request,
|
||||
Method = "CallMethod",
|
||||
Parameters = new object[] { request },
|
||||
SourceProcessId = _serverId,
|
||||
TargetProcessId = targetProcessId
|
||||
};
|
||||
|
||||
var response = await _messageRouter.SendRequestAsync(
|
||||
message,
|
||||
msg => _connectionManager.SendMessageAsync(targetProcessId, msg),
|
||||
_configuration.MethodCallTimeout);
|
||||
|
||||
if (response.Type == MessageType.Error) {
|
||||
throw new MethodCallException(response.Error);
|
||||
}
|
||||
|
||||
if (response.Result is MethodCallResponse methodResponse) {
|
||||
if (!methodResponse.IsSuccess) {
|
||||
throw new MethodCallException("方法调用失败", methodResponse.Exception);
|
||||
}
|
||||
|
||||
return methodResponse.Result;
|
||||
}
|
||||
|
||||
return response.Result;
|
||||
} catch (Exception ex) when (!(ex is IPCException)) {
|
||||
throw new MethodCallException($"调用方法失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendNotificationAsync(string targetProcessId, string method, params object[] parameters) {
|
||||
if (!IsRunning)
|
||||
throw new InvalidOperationException("服务器未运行");
|
||||
|
||||
var message = new IPCMessage {
|
||||
Type = MessageType.Notification,
|
||||
Method = method,
|
||||
Parameters = parameters,
|
||||
SourceProcessId = _serverId,
|
||||
TargetProcessId = targetProcessId
|
||||
};
|
||||
|
||||
await _connectionManager.SendMessageAsync(targetProcessId, message);
|
||||
}
|
||||
|
||||
public async Task BroadcastNotificationAsync(string method, params object[] parameters) {
|
||||
if (!IsRunning)
|
||||
throw new InvalidOperationException("服务器未运行");
|
||||
|
||||
var message = new IPCMessage {
|
||||
Type = MessageType.Notification,
|
||||
Method = method,
|
||||
Parameters = parameters,
|
||||
SourceProcessId = _serverId
|
||||
};
|
||||
|
||||
await _connectionManager.BroadcastMessageAsync(message);
|
||||
}
|
||||
|
||||
public async Task RegisterServiceAsync<T>(string serviceName, T serviceInstance) {
|
||||
await _serviceRegistry.RegisterServiceAsync(serviceName, serviceInstance);
|
||||
}
|
||||
|
||||
public async Task UnregisterServiceAsync(string serviceName) {
|
||||
await _serviceRegistry.UnregisterServiceAsync(serviceName);
|
||||
}
|
||||
|
||||
private void InitializeEventHandlers() {
|
||||
_connectionManager.ProcessConnected += OnProcessConnected;
|
||||
_connectionManager.ProcessDisconnected += OnProcessDisconnected;
|
||||
|
||||
_heartbeatManager.ProcessTimeout += async (sender, processId) => {
|
||||
Console.WriteLine($"进程 {processId} 心跳超时,断开连接");
|
||||
await _connectionManager.DisconnectFromProcessAsync(processId);
|
||||
};
|
||||
}
|
||||
|
||||
private void OnProcessConnected(object sender, ProcessInfo processInfo) {
|
||||
Console.WriteLine($"进程已连接: {processInfo.ProcessId}");
|
||||
|
||||
if (_configuration.EnableHeartbeat) {
|
||||
_heartbeatManager.StartMonitoring(processInfo.ProcessId);
|
||||
}
|
||||
|
||||
ProcessConnected?.Invoke(this, processInfo);
|
||||
}
|
||||
|
||||
private void OnProcessDisconnected(object sender, ProcessInfo processInfo) {
|
||||
Console.WriteLine($"进程已断开: {processInfo.ProcessId}");
|
||||
|
||||
_heartbeatManager.StopMonitoring(processInfo.ProcessId);
|
||||
ProcessDisconnected?.Invoke(this, processInfo);
|
||||
}
|
||||
|
||||
private async Task HandleIncomingMessage(IPCMessage message) {
|
||||
try {
|
||||
// 更新心跳
|
||||
if (_configuration.EnableHeartbeat && message.Type == MessageType.Heartbeat) {
|
||||
_heartbeatManager.UpdateHeartbeat(message.SourceProcessId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
var response = await _messageRouter.HandleMessageAsync(message);
|
||||
if (response != null) {
|
||||
await _connectionManager.SendMessageAsync(message.SourceProcessId, response);
|
||||
}
|
||||
|
||||
MessageReceived?.Invoke(this, message);
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"处理消息时发生错误: {ex.Message}");
|
||||
|
||||
// 发送错误响应
|
||||
var errorResponse = new IPCMessage {
|
||||
Id = message.Id,
|
||||
Type = MessageType.Error,
|
||||
Error = ex.Message,
|
||||
SourceProcessId = _serverId,
|
||||
TargetProcessId = message.SourceProcessId
|
||||
};
|
||||
|
||||
await _connectionManager.SendMessageAsync(message.SourceProcessId, errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
|
||||
try {
|
||||
StopAsync().Wait(TimeSpan.FromSeconds(10));
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"释放资源时发生错误: {ex.Message}");
|
||||
}
|
||||
|
||||
_connectionManager?.Dispose();
|
||||
_heartbeatManager?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
93
IpcLibrary/Server/IPCServerExtensions.cs
Normal file
93
IpcLibrary/Server/IPCServerExtensions.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Server {
|
||||
/// <summary>
|
||||
/// 服务器扩展方法
|
||||
/// </summary>
|
||||
public static class IPCServerExtensions {
|
||||
/// <summary>
|
||||
/// 调用无返回值的方法
|
||||
/// </summary>
|
||||
public static async Task CallMethodAsync(this IIPCServer server, string targetProcessId, string serviceName, string methodName, params object[] parameters) {
|
||||
await server.CallMethodAsync<object>(targetProcessId, serviceName, methodName, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册多个服务
|
||||
/// </summary>
|
||||
public static async Task RegisterServicesAsync(this IIPCServer server, Dictionary<string, object> services) {
|
||||
foreach (var kvp in services) {
|
||||
await server.RegisterServiceAsync(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取在线进程数量
|
||||
/// </summary>
|
||||
public static int GetOnlineProcessCount(this IIPCServer server) {
|
||||
return server.ConnectedProcesses.Count(p => p.Status == ProcessStatus.Connected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定状态的进程
|
||||
/// </summary>
|
||||
public static IEnumerable<ProcessInfo> GetProcessesByStatus(this IIPCServer server, ProcessStatus status) {
|
||||
return server.ConnectedProcesses.Where(p => p.Status == status);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定状态的所有进程发送通知
|
||||
/// </summary>
|
||||
public static async Task BroadcastToStatusAsync(this IIPCServer server, ProcessStatus status, string method, params object[] parameters) {
|
||||
var processes = server.GetProcessesByStatus(status);
|
||||
var tasks = processes.Select(p => server.SendNotificationAsync(p.ProcessId, method, parameters));
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用重试机制调用方法
|
||||
/// </summary>
|
||||
public static async Task<T> CallMethodWithRetryAsync<T>(this IIPCServer server, string targetProcessId, string serviceName, string methodName, int maxRetries = 3, TimeSpan? retryDelay = null, params object[] parameters) {
|
||||
var delay = retryDelay ?? TimeSpan.FromSeconds(1);
|
||||
Exception lastException = null;
|
||||
|
||||
for (int attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await server.CallMethodAsync<T>(targetProcessId, serviceName, methodName, parameters);
|
||||
} catch (Exception ex) {
|
||||
lastException = ex;
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new MethodCallException($"方法调用在 {maxRetries + 1} 次尝试后仍然失败", lastException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待指定数量的进程连接
|
||||
/// </summary>
|
||||
public static async Task<bool> WaitForProcessesAsync(this IIPCServer server, int expectedCount, TimeSpan timeout) {
|
||||
var startTime = DateTime.UtcNow;
|
||||
|
||||
while (DateTime.UtcNow - startTime < timeout) {
|
||||
if (server.GetOnlineProcessCount() >= expectedCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
IpcLibrary/Server/IPCServerFactory.cs
Normal file
37
IpcLibrary/Server/IPCServerFactory.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using IpcLibrary.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Server {
|
||||
/// <summary>
|
||||
/// IPC服务器工厂
|
||||
/// </summary>
|
||||
public static class IPCServerFactory {
|
||||
/// <summary>
|
||||
/// 创建默认配置的服务器
|
||||
/// </summary>
|
||||
public static IIPCServer CreateServer() {
|
||||
return new IPCServer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建自定义配置的服务器
|
||||
/// </summary>
|
||||
public static IIPCServer CreateServer(IPCConfiguration configuration) {
|
||||
return new IPCServer(configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并启动服务器
|
||||
/// </summary>
|
||||
public static async Task<IIPCServer> CreateAndStartAsync(string address, IPCConfiguration configuration = null) {
|
||||
var server = new IPCServer(configuration);
|
||||
await server.StartAsync(address);
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
90
IpcLibrary/Server/ServerMonitor.cs
Normal file
90
IpcLibrary/Server/ServerMonitor.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using IpcLibrary.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Server {
|
||||
/// <summary>
|
||||
/// 服务器状态监控器
|
||||
/// </summary>
|
||||
public class ServerMonitor : IDisposable {
|
||||
private readonly IPCServer _server;
|
||||
private readonly System.Timers.Timer _monitorTimer;
|
||||
private readonly Dictionary<string, DateTime> _processLastSeen;
|
||||
private bool _disposed;
|
||||
|
||||
public event EventHandler<ServerStatistics> StatisticsUpdated;
|
||||
|
||||
public ServerMonitor(IPCServer server, TimeSpan monitorInterval) {
|
||||
_server = server ?? throw new ArgumentNullException(nameof(server));
|
||||
_processLastSeen = new Dictionary<string, DateTime>();
|
||||
|
||||
_monitorTimer = new System.Timers.Timer(monitorInterval.TotalMilliseconds);
|
||||
_monitorTimer.Elapsed += OnMonitorTimer;
|
||||
_monitorTimer.Start();
|
||||
|
||||
_server.ProcessConnected += OnProcessConnected;
|
||||
_server.ProcessDisconnected += OnProcessDisconnected;
|
||||
}
|
||||
|
||||
private void OnProcessConnected(object sender, ProcessInfo processInfo) {
|
||||
lock (_processLastSeen) {
|
||||
_processLastSeen[processInfo.ProcessId] = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnProcessDisconnected(object sender, ProcessInfo processInfo) {
|
||||
lock (_processLastSeen) {
|
||||
_processLastSeen.Remove(processInfo.ProcessId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMonitorTimer(object sender, System.Timers.ElapsedEventArgs e) {
|
||||
try {
|
||||
var statistics = GatherStatistics();
|
||||
StatisticsUpdated?.Invoke(this, statistics);
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"收集统计信息时发生错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private ServerStatistics GatherStatistics() {
|
||||
var connectedProcesses = _server.ConnectedProcesses;
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return new ServerStatistics {
|
||||
Timestamp = now,
|
||||
TotalConnectedProcesses = connectedProcesses.Count,
|
||||
OnlineProcesses = connectedProcesses.Count(p => p.Status == ProcessStatus.Connected),
|
||||
DisconnectedProcesses = connectedProcesses.Count(p => p.Status == ProcessStatus.Disconnected),
|
||||
ReconnectingProcesses = connectedProcesses.Count(p => p.Status == ProcessStatus.Reconnecting),
|
||||
AverageHeartbeatDelay = CalculateAverageHeartbeatDelay(connectedProcesses, now),
|
||||
ServerUptime = now - _server.StartTime // 需要在IPCServer中添加StartTime属性
|
||||
};
|
||||
}
|
||||
|
||||
private TimeSpan CalculateAverageHeartbeatDelay(IReadOnlyList<ProcessInfo> processes, DateTime now) {
|
||||
var delays = processes
|
||||
.Where(p => p.Status == ProcessStatus.Connected)
|
||||
.Select(p => now - p.LastHeartbeat)
|
||||
.Where(delay => delay.TotalSeconds >= 0);
|
||||
|
||||
if (!delays.Any())
|
||||
return TimeSpan.Zero;
|
||||
|
||||
var averageTicks = delays.Select(d => d.Ticks).Average();
|
||||
return new TimeSpan((long)averageTicks);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
_monitorTimer?.Stop();
|
||||
_monitorTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
20
IpcLibrary/Server/ServerStatistics.cs
Normal file
20
IpcLibrary/Server/ServerStatistics.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Server {
|
||||
/// <summary>
|
||||
/// 服务器统计信息
|
||||
/// </summary>
|
||||
public class ServerStatistics {
|
||||
public DateTime Timestamp { get; set; }
|
||||
public int TotalConnectedProcesses { get; set; }
|
||||
public int OnlineProcesses { get; set; }
|
||||
public int DisconnectedProcesses { get; set; }
|
||||
public int ReconnectingProcesses { get; set; }
|
||||
public TimeSpan AverageHeartbeatDelay { get; set; }
|
||||
public TimeSpan ServerUptime { get; set; }
|
||||
}
|
||||
}
|
||||
100
IpcLibrary/Services/HeartbeatManager.cs
Normal file
100
IpcLibrary/Services/HeartbeatManager.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using IpcLibrary.Core;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Services {
|
||||
/// <summary>
|
||||
/// 心跳管理器实现
|
||||
/// </summary>
|
||||
public class HeartbeatManager : IHeartbeatManager, IDisposable {
|
||||
private readonly ConcurrentDictionary<string, ProcessHeartbeat> _heartbeats;
|
||||
private readonly Timer _monitorTimer;
|
||||
private readonly object _lock = new object();
|
||||
private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(5);
|
||||
private TimeSpan _timeoutThreshold = TimeSpan.FromSeconds(15);
|
||||
private bool _disposed;
|
||||
|
||||
public event EventHandler<string> ProcessTimeout;
|
||||
|
||||
public HeartbeatManager() {
|
||||
_heartbeats = new ConcurrentDictionary<string, ProcessHeartbeat>();
|
||||
_monitorTimer = new Timer(MonitorHeartbeats, null, _heartbeatInterval, _heartbeatInterval);
|
||||
}
|
||||
|
||||
public void StartMonitoring(string processId) {
|
||||
if (string.IsNullOrEmpty(processId))
|
||||
throw new ArgumentException("进程ID不能为空", nameof(processId));
|
||||
|
||||
var heartbeat = new ProcessHeartbeat {
|
||||
ProcessId = processId,
|
||||
LastHeartbeat = DateTime.UtcNow,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
_heartbeats.AddOrUpdate(processId, heartbeat, (key, old) => heartbeat);
|
||||
}
|
||||
|
||||
public void StopMonitoring(string processId) {
|
||||
if (_heartbeats.TryGetValue(processId, out var heartbeat)) {
|
||||
heartbeat.IsActive = false;
|
||||
_heartbeats.TryRemove(processId, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateHeartbeat(string processId) {
|
||||
if (_heartbeats.TryGetValue(processId, out var heartbeat)) {
|
||||
heartbeat.LastHeartbeat = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHeartbeatInterval(TimeSpan interval) {
|
||||
if (interval <= TimeSpan.Zero)
|
||||
throw new ArgumentException("心跳间隔必须大于零", nameof(interval));
|
||||
|
||||
lock (_lock) {
|
||||
_heartbeatInterval = interval;
|
||||
_monitorTimer?.Change(interval, interval);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTimeoutThreshold(TimeSpan timeout) {
|
||||
if (timeout <= TimeSpan.Zero)
|
||||
throw new ArgumentException("超时阈值必须大于零", nameof(timeout));
|
||||
|
||||
_timeoutThreshold = timeout;
|
||||
}
|
||||
|
||||
private void MonitorHeartbeats(object state) {
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var timedOutProcesses = new List<string>();
|
||||
|
||||
foreach (var kvp in _heartbeats) {
|
||||
var heartbeat = kvp.Value;
|
||||
if (heartbeat.IsActive && now - heartbeat.LastHeartbeat > _timeoutThreshold) {
|
||||
timedOutProcesses.Add(kvp.Key);
|
||||
heartbeat.IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var processId in timedOutProcesses) {
|
||||
ProcessTimeout?.Invoke(this, processId);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
_monitorTimer?.Dispose();
|
||||
_heartbeats.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
108
IpcLibrary/Services/MessageRouter.cs
Normal file
108
IpcLibrary/Services/MessageRouter.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
|
||||
namespace IpcLibrary.Services {
|
||||
/// <summary>
|
||||
/// 消息路由器
|
||||
/// </summary>
|
||||
public class MessageRouter {
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<IPCMessage>> _pendingRequests;
|
||||
private readonly MethodCallHandler _methodCallHandler;
|
||||
private readonly Timer _timeoutTimer;
|
||||
|
||||
public MessageRouter(MethodCallHandler methodCallHandler) {
|
||||
_pendingRequests = new ConcurrentDictionary<string, TaskCompletionSource<IPCMessage>>();
|
||||
_methodCallHandler = methodCallHandler ?? throw new ArgumentNullException(nameof(methodCallHandler));
|
||||
_timeoutTimer = new Timer(CheckTimeouts, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
|
||||
}
|
||||
|
||||
public async Task<IPCMessage> SendRequestAsync(IPCMessage request, Func<IPCMessage, Task<bool>> sendAction, TimeSpan timeout) {
|
||||
var tcs = new TaskCompletionSource<IPCMessage>();
|
||||
_pendingRequests[request.Id] = tcs;
|
||||
|
||||
try {
|
||||
if (!await sendAction(request)) {
|
||||
_pendingRequests.TryRemove(request.Id, out _);
|
||||
throw new ConnectionException("发送请求失败");
|
||||
}
|
||||
|
||||
using var timeoutCts = new CancellationTokenSource(timeout);
|
||||
timeoutCts.Token.Register(() => tcs.TrySetException(new Core.Exceptions.TimeoutException("请求超时")));
|
||||
|
||||
return await tcs.Task;
|
||||
} finally {
|
||||
_pendingRequests.TryRemove(request.Id, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IPCMessage> HandleMessageAsync(IPCMessage message) {
|
||||
switch (message.Type) {
|
||||
case MessageType.Request:
|
||||
return await HandleRequestAsync(message);
|
||||
|
||||
case MessageType.Response:
|
||||
HandleResponse(message);
|
||||
return null;
|
||||
|
||||
case MessageType.Notification:
|
||||
await HandleNotificationAsync(message);
|
||||
return null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IPCMessage> HandleRequestAsync(IPCMessage request) {
|
||||
try {
|
||||
if (request.Parameters?.Length > 0 && request.Parameters[0] is MethodCallRequest methodCall) {
|
||||
var response = await _methodCallHandler.HandleMethodCallAsync(methodCall);
|
||||
|
||||
return new IPCMessage {
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Type = MessageType.Response,
|
||||
Result = response,
|
||||
SourceProcessId = request.TargetProcessId,
|
||||
TargetProcessId = request.SourceProcessId
|
||||
};
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
return new IPCMessage {
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Type = MessageType.Error,
|
||||
Error = ex.Message,
|
||||
SourceProcessId = request.TargetProcessId,
|
||||
TargetProcessId = request.SourceProcessId
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleResponse(IPCMessage response) {
|
||||
if (_pendingRequests.TryGetValue(response.Id, out var tcs)) {
|
||||
tcs.SetResult(response);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleNotificationAsync(IPCMessage notification) {
|
||||
// 通知类型消息的处理逻辑
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void CheckTimeouts(object state) {
|
||||
var expiredRequests = _pendingRequests.Where(kvp =>
|
||||
DateTime.UtcNow - DateTime.MinValue > TimeSpan.FromMinutes(5)) // 简化的超时检查
|
||||
.ToList();
|
||||
|
||||
foreach (var kvp in expiredRequests) {
|
||||
if (_pendingRequests.TryRemove(kvp.Key, out var tcs)) {
|
||||
tcs.TrySetException(new Core.Exceptions.TimeoutException("请求超时"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
IpcLibrary/Services/MethodCallHandler.cs
Normal file
84
IpcLibrary/Services/MethodCallHandler.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using IpcLibrary.Core;
|
||||
using IpcLibrary.Core.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Services {
|
||||
/// <summary>
|
||||
/// 方法调用处理器
|
||||
/// </summary>
|
||||
public class MethodCallHandler {
|
||||
private readonly IServiceRegistry _serviceRegistry;
|
||||
|
||||
public MethodCallHandler(IServiceRegistry serviceRegistry) {
|
||||
_serviceRegistry = serviceRegistry ?? throw new ArgumentNullException(nameof(serviceRegistry));
|
||||
}
|
||||
|
||||
public async Task<MethodCallResponse> HandleMethodCallAsync(MethodCallRequest request) {
|
||||
try {
|
||||
var service = _serviceRegistry.GetService(request.ServiceName);
|
||||
if (service == null) {
|
||||
return new MethodCallResponse {
|
||||
Exception = new IPCException($"服务 '{request.ServiceName}' 未找到")
|
||||
};
|
||||
}
|
||||
|
||||
var serviceType = service.GetType();
|
||||
MethodInfo method = null;
|
||||
|
||||
// 查找匹配的方法
|
||||
if (request.ParameterTypes != null && request.ParameterTypes.Length > 0) {
|
||||
method = serviceType.GetMethod(request.MethodName, request.ParameterTypes);
|
||||
} else {
|
||||
// 如果没有提供参数类型,尝试按名称和参数数量匹配
|
||||
var methods = serviceType.GetMethods().Where(m => m.Name == request.MethodName);
|
||||
var paramCount = request.Parameters?.Length ?? 0;
|
||||
method = methods.FirstOrDefault(m => m.GetParameters().Length == paramCount);
|
||||
}
|
||||
|
||||
if (method == null) {
|
||||
return new MethodCallResponse {
|
||||
Exception = new IPCException($"方法 '{request.MethodName}' 在服务 '{request.ServiceName}' 中未找到")
|
||||
};
|
||||
}
|
||||
|
||||
// 调用方法
|
||||
object result;
|
||||
if (method.ReturnType == typeof(Task)) {
|
||||
// 异步方法无返回值
|
||||
var task = (Task)method.Invoke(service, request.Parameters);
|
||||
await task;
|
||||
result = null;
|
||||
} else if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) {
|
||||
// 异步方法有返回值
|
||||
var task = (Task)method.Invoke(service, request.Parameters);
|
||||
await task;
|
||||
|
||||
var resultProperty = task.GetType().GetProperty("Result");
|
||||
result = resultProperty?.GetValue(task);
|
||||
} else {
|
||||
// 同步方法
|
||||
result = method.Invoke(service, request.Parameters);
|
||||
}
|
||||
|
||||
return new MethodCallResponse {
|
||||
Result = result,
|
||||
ResultType = method.ReturnType
|
||||
};
|
||||
} catch (TargetInvocationException ex) {
|
||||
return new MethodCallResponse {
|
||||
Exception = ex.InnerException ?? ex
|
||||
};
|
||||
} catch (Exception ex) {
|
||||
return new MethodCallResponse {
|
||||
Exception = ex
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
16
IpcLibrary/Services/ProcessHeartbeat.cs
Normal file
16
IpcLibrary/Services/ProcessHeartbeat.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Services {
|
||||
/// <summary>
|
||||
/// 进程心跳信息
|
||||
/// </summary>
|
||||
internal class ProcessHeartbeat {
|
||||
public string ProcessId { get; set; }
|
||||
public DateTime LastHeartbeat { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
}
|
||||
14
IpcLibrary/Services/ServiceInfo.cs
Normal file
14
IpcLibrary/Services/ServiceInfo.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace IpcLibrary.Services {
|
||||
/// <summary>
|
||||
/// 服务信息
|
||||
/// </summary>
|
||||
public class ServiceInfo {
|
||||
public string ServiceName { get; set; }
|
||||
public object ServiceInstance { get; set; }
|
||||
public Type ServiceType { get; set; }
|
||||
public DateTime RegisterTime { get; set; }
|
||||
public Dictionary<string, MethodInfo> Methods { get; set; }
|
||||
}
|
||||
}
|
||||
94
IpcLibrary/Services/ServiceRegistry.cs
Normal file
94
IpcLibrary/Services/ServiceRegistry.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using IpcLibrary.Core;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IpcLibrary.Services {
|
||||
/// <summary>
|
||||
/// 服务注册器实现
|
||||
/// </summary>
|
||||
public class ServiceRegistry : IServiceRegistry {
|
||||
private readonly ConcurrentDictionary<string, ServiceInfo> _services;
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public ServiceRegistry() {
|
||||
_services = new ConcurrentDictionary<string, ServiceInfo>();
|
||||
}
|
||||
|
||||
public Task RegisterServiceAsync<T>(string serviceName, T serviceInstance) {
|
||||
if (string.IsNullOrEmpty(serviceName))
|
||||
throw new ArgumentException("服务名称不能为空", nameof(serviceName));
|
||||
|
||||
if (serviceInstance == null)
|
||||
throw new ArgumentNullException(nameof(serviceInstance));
|
||||
|
||||
var serviceInfo = new ServiceInfo {
|
||||
ServiceName = serviceName,
|
||||
ServiceInstance = serviceInstance,
|
||||
ServiceType = typeof(T),
|
||||
RegisterTime = DateTime.UtcNow,
|
||||
Methods = GetServiceMethods(typeof(T))
|
||||
};
|
||||
|
||||
_services.AddOrUpdate(serviceName, serviceInfo, (key, old) => serviceInfo);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UnregisterServiceAsync(string serviceName) {
|
||||
if (string.IsNullOrEmpty(serviceName))
|
||||
throw new ArgumentException("服务名称不能为空", nameof(serviceName));
|
||||
|
||||
_services.TryRemove(serviceName, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public object GetService(string serviceName) {
|
||||
return _services.TryGetValue(serviceName, out var serviceInfo)
|
||||
? serviceInfo.ServiceInstance
|
||||
: null;
|
||||
}
|
||||
|
||||
public T GetService<T>(string serviceName) {
|
||||
var service = GetService(serviceName);
|
||||
return service is T typedService ? typedService : default(T);
|
||||
}
|
||||
|
||||
public bool IsServiceRegistered(string serviceName) {
|
||||
return _services.ContainsKey(serviceName);
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> GetRegisteredServices() {
|
||||
return _services.Keys.ToList();
|
||||
}
|
||||
|
||||
public ServiceInfo GetServiceInfo(string serviceName) {
|
||||
return _services.TryGetValue(serviceName, out var serviceInfo) ? serviceInfo : null;
|
||||
}
|
||||
|
||||
private Dictionary<string, MethodInfo> GetServiceMethods(Type serviceType) {
|
||||
var methods = new Dictionary<string, MethodInfo>();
|
||||
|
||||
foreach (var method in serviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)) {
|
||||
// 排除Object的基本方法
|
||||
if (method.DeclaringType == typeof(object))
|
||||
continue;
|
||||
|
||||
var key = GetMethodKey(method);
|
||||
methods[key] = method;
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
private string GetMethodKey(MethodInfo method) {
|
||||
var parameters = method.GetParameters();
|
||||
var paramTypes = string.Join(",", parameters.Select(p => p.ParameterType.FullName));
|
||||
return $"{method.Name}({paramTypes})";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user