23 changed files with 33 additions and 1820 deletions
@ -1,28 +0,0 @@ |
|||
using System.Threading.Channels; |
|||
|
|||
namespace CellularManagement.Application.Pipeline; |
|||
|
|||
public interface IWebSocketMessageHandler |
|||
{ |
|||
string HandlerName { get; } |
|||
int Priority { get; } |
|||
bool CanHandle(string messageType); |
|||
Task HandleAsync(WebSocketMessageContext context, CancellationToken cancellationToken); |
|||
} |
|||
|
|||
public class WebSocketMessageContext |
|||
{ |
|||
public string ConnectionId { get; set; } = string.Empty; |
|||
public string MessageType { get; set; } = string.Empty; |
|||
public byte[] Payload { get; set; } = Array.Empty<byte>(); |
|||
public Dictionary<string, object> Properties { get; } = new(); |
|||
public bool IsHandled { get; set; } |
|||
public Exception? Error { get; set; } |
|||
} |
|||
|
|||
public interface IWebSocketMessagePipeline |
|||
{ |
|||
Task ProcessMessageAsync(WebSocketMessageContext context, CancellationToken cancellationToken); |
|||
void RegisterHandler(IWebSocketMessageHandler handler); |
|||
void UnregisterHandler(string handlerName); |
|||
} |
@ -1,16 +0,0 @@ |
|||
using System.Net.WebSockets; |
|||
using CellularManagement.Domain.Entities; |
|||
|
|||
namespace CellularManagement.Application.Services; |
|||
|
|||
public interface IWebSocketService |
|||
{ |
|||
Task<string> AcceptConnectionAsync(WebSocket webSocket); |
|||
Task<bool> CloseConnectionAsync(string connectionId); |
|||
Task<bool> SendMessageAsync(string connectionId, byte[] message); |
|||
Task<bool> BroadcastMessageAsync(byte[] message); |
|||
Task<bool> SendMessageToUserAsync(string userId, byte[] message); |
|||
Task AssociateUserAsync(string connectionId, string userId); |
|||
Task<WebSocketConnection?> GetConnectionAsync(string connectionId); |
|||
Task<IEnumerable<WebSocketConnection>> GetUserConnectionsAsync(string userId); |
|||
} |
@ -1,137 +0,0 @@ |
|||
using System.Net.WebSockets; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Application.Services; |
|||
using CellularManagement.Application.Pipeline; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Events; |
|||
|
|||
namespace CellularManagement.Application.Services; |
|||
|
|||
public class WebSocketService : IWebSocketService |
|||
{ |
|||
private readonly ICacheService _cacheService; |
|||
private readonly ILogger<WebSocketService> _logger; |
|||
private readonly IWebSocketMessagePipeline _messagePipeline; |
|||
private readonly IMediator _mediator; |
|||
private const string CONNECTION_PREFIX = "ws_connection_"; |
|||
private const string USER_CONNECTION_PREFIX = "ws_user_"; |
|||
|
|||
public WebSocketService( |
|||
ICacheService cacheService, |
|||
ILogger<WebSocketService> logger, |
|||
IWebSocketMessagePipeline messagePipeline, |
|||
IMediator mediator) |
|||
{ |
|||
_cacheService = cacheService; |
|||
_logger = logger; |
|||
_messagePipeline = messagePipeline; |
|||
_mediator = mediator; |
|||
} |
|||
|
|||
public async Task<string> AcceptConnectionAsync(WebSocket webSocket) |
|||
{ |
|||
var connectionId = Guid.NewGuid().ToString(); |
|||
var connection = WebSocketConnection.Create(connectionId); |
|||
|
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
_cacheService.Set(connectionKey, webSocket); |
|||
_cacheService.Set(connectionId, connection); |
|||
|
|||
_logger.LogInformation("WebSocket connection accepted: {ConnectionId}", connectionId); |
|||
return connectionId; |
|||
} |
|||
|
|||
public async Task<bool> CloseConnectionAsync(string connectionId) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var userKey = $"{USER_CONNECTION_PREFIX}{connectionId}"; |
|||
|
|||
var connection = _cacheService.Get<WebSocketConnection>(connectionId); |
|||
if (connection != null) |
|||
{ |
|||
connection.Close(); |
|||
_cacheService.Set(connectionId, connection); |
|||
} |
|||
|
|||
_cacheService.Remove(connectionKey); |
|||
_cacheService.Remove(userKey); |
|||
|
|||
_logger.LogInformation("WebSocket connection closed: {ConnectionId}", connectionId); |
|||
return true; |
|||
} |
|||
|
|||
public async Task<bool> SendMessageAsync(string connectionId, byte[] message) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var webSocket = _cacheService.Get<WebSocket>(connectionKey); |
|||
|
|||
if (webSocket?.State == WebSocketState.Open) |
|||
{ |
|||
await webSocket.SendAsync( |
|||
new ArraySegment<byte>(message), |
|||
WebSocketMessageType.Text, |
|||
true, |
|||
CancellationToken.None); |
|||
|
|||
await _mediator.Publish(new WebSocketMessageSentEvent( |
|||
connectionId, |
|||
"text", |
|||
message)); |
|||
|
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public async Task<bool> BroadcastMessageAsync(byte[] message) |
|||
{ |
|||
// 注意:这里需要实现广播逻辑
|
|||
// 由于ICacheService的限制,可能需要使用其他方式实现
|
|||
return false; |
|||
} |
|||
|
|||
public async Task<bool> SendMessageToUserAsync(string userId, byte[] message) |
|||
{ |
|||
// 注意:这里需要实现向特定用户发送消息的逻辑
|
|||
// 由于ICacheService的限制,可能需要使用其他方式实现
|
|||
return false; |
|||
} |
|||
|
|||
public async Task AssociateUserAsync(string connectionId, string userId) |
|||
{ |
|||
var connection = _cacheService.Get<WebSocketConnection>(connectionId); |
|||
if (connection != null) |
|||
{ |
|||
connection.AssociateUser(userId); |
|||
_cacheService.Set(connectionId, connection); |
|||
|
|||
var userKey = $"{USER_CONNECTION_PREFIX}{connectionId}"; |
|||
_cacheService.Set(userKey, userId); |
|||
} |
|||
} |
|||
|
|||
public async Task<WebSocketConnection?> GetConnectionAsync(string connectionId) |
|||
{ |
|||
return _cacheService.Get<WebSocketConnection>(connectionId); |
|||
} |
|||
|
|||
public async Task<IEnumerable<WebSocketConnection>> GetUserConnectionsAsync(string userId) |
|||
{ |
|||
// 注意:这里需要实现获取用户所有连接的逻辑
|
|||
// 由于ICacheService的限制,可能需要使用其他方式实现
|
|||
return Enumerable.Empty<WebSocketConnection>(); |
|||
} |
|||
|
|||
public async Task ProcessMessageAsync(string connectionId, string messageType, byte[] payload) |
|||
{ |
|||
var context = new WebSocketMessageContext |
|||
{ |
|||
ConnectionId = connectionId, |
|||
MessageType = messageType, |
|||
Payload = payload |
|||
}; |
|||
|
|||
await _messagePipeline.ProcessMessageAsync(context, CancellationToken.None); |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace CellularManagement.Domain.Common; |
|||
|
|||
public interface IAggregateRoot |
|||
{ |
|||
// 聚合根接口,用于标识领域模型中的聚合根
|
|||
} |
@ -1,66 +0,0 @@ |
|||
using System.Net.WebSockets; |
|||
using System.Text.Json.Serialization; |
|||
|
|||
namespace CellularManagement.Domain.Entities; |
|||
|
|||
public class WebSocketConnection |
|||
{ |
|||
[JsonPropertyName("connectionId")] |
|||
public string ConnectionId { get; set; } |
|||
|
|||
[JsonPropertyName("userId")] |
|||
public string? UserId { get; set; } |
|||
|
|||
[JsonPropertyName("connectedAt")] |
|||
public DateTime ConnectedAt { get; set; } |
|||
|
|||
[JsonPropertyName("lastActivityAt")] |
|||
public DateTime? LastActivityAt { get; set; } |
|||
|
|||
[JsonPropertyName("state")] |
|||
public WebSocketState State { get; set; } |
|||
|
|||
// 添加无参构造函数用于反序列化
|
|||
public WebSocketConnection() |
|||
{ |
|||
ConnectionId = string.Empty; |
|||
ConnectedAt = DateTime.UtcNow; |
|||
State = WebSocketState.Open; |
|||
} |
|||
|
|||
// 添加带参数的构造函数
|
|||
[JsonConstructor] |
|||
public WebSocketConnection(string connectionId, string? userId, DateTime connectedAt, DateTime? lastActivityAt, WebSocketState state) |
|||
{ |
|||
ConnectionId = connectionId; |
|||
UserId = userId; |
|||
ConnectedAt = connectedAt; |
|||
LastActivityAt = lastActivityAt; |
|||
State = state; |
|||
} |
|||
|
|||
public static WebSocketConnection Create(string connectionId) |
|||
{ |
|||
return new WebSocketConnection |
|||
{ |
|||
ConnectionId = connectionId, |
|||
ConnectedAt = DateTime.UtcNow, |
|||
State = WebSocketState.Open |
|||
}; |
|||
} |
|||
|
|||
public void AssociateUser(string userId) |
|||
{ |
|||
UserId = userId; |
|||
} |
|||
|
|||
public void UpdateLastActivity() |
|||
{ |
|||
LastActivityAt = DateTime.UtcNow; |
|||
} |
|||
|
|||
public void Close() |
|||
{ |
|||
State = WebSocketState.Closed; |
|||
} |
|||
} |
@ -1,35 +0,0 @@ |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Domain.Events; |
|||
|
|||
public class WebSocketMessageReceivedEvent : INotification |
|||
{ |
|||
public string ConnectionId { get; } |
|||
public string MessageType { get; } |
|||
public byte[] Payload { get; } |
|||
public DateTime Timestamp { get; } |
|||
|
|||
public WebSocketMessageReceivedEvent(string connectionId, string messageType, byte[] payload) |
|||
{ |
|||
ConnectionId = connectionId; |
|||
MessageType = messageType; |
|||
Payload = payload; |
|||
Timestamp = DateTime.UtcNow; |
|||
} |
|||
} |
|||
|
|||
public class WebSocketMessageSentEvent : INotification |
|||
{ |
|||
public string ConnectionId { get; } |
|||
public string MessageType { get; } |
|||
public byte[] Payload { get; } |
|||
public DateTime Timestamp { get; } |
|||
|
|||
public WebSocketMessageSentEvent(string connectionId, string messageType, byte[] payload) |
|||
{ |
|||
ConnectionId = connectionId; |
|||
MessageType = messageType; |
|||
Payload = payload; |
|||
Timestamp = DateTime.UtcNow; |
|||
} |
|||
} |
@ -1,9 +0,0 @@ |
|||
namespace CellularManagement.Infrastructure.Configurations; |
|||
|
|||
public class WebSocketConfiguration |
|||
{ |
|||
public const string SectionName = "WebSocket"; |
|||
|
|||
public int Port { get; set; } = 5202; |
|||
public string Path { get; set; } = "/ws"; |
|||
} |
@ -1,196 +0,0 @@ |
|||
using System.Net.WebSockets; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Application.Services; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using System.Text.Json; |
|||
|
|||
namespace CellularManagement.Infrastructure.Distributed; |
|||
|
|||
public class DistributedWebSocketManager : IWebSocketService |
|||
{ |
|||
private readonly IDistributedCache _distributedCache; |
|||
private readonly ILogger<DistributedWebSocketManager> _logger; |
|||
private readonly string _nodeId; |
|||
private const string CONNECTION_PREFIX = "ws_connection_"; |
|||
private const string USER_PREFIX = "ws_user_"; |
|||
private const string NODE_PREFIX = "ws_node_"; |
|||
|
|||
public DistributedWebSocketManager( |
|||
IDistributedCache distributedCache, |
|||
ILogger<DistributedWebSocketManager> logger) |
|||
{ |
|||
_distributedCache = distributedCache; |
|||
_logger = logger; |
|||
_nodeId = Guid.NewGuid().ToString(); |
|||
} |
|||
|
|||
public async Task<string> AcceptConnectionAsync(System.Net.WebSockets.WebSocket webSocket) |
|||
{ |
|||
var connectionId = Guid.NewGuid().ToString(); |
|||
var connection = WebSocketConnection.Create(connectionId); |
|||
|
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var nodeKey = $"{NODE_PREFIX}{_nodeId}"; |
|||
|
|||
// 存储连接信息
|
|||
await _distributedCache.SetStringAsync( |
|||
connectionKey, |
|||
JsonSerializer.Serialize(connection), |
|||
new DistributedCacheEntryOptions |
|||
{ |
|||
SlidingExpiration = TimeSpan.FromMinutes(30) |
|||
}); |
|||
|
|||
// 更新节点连接列表
|
|||
await AddConnectionToNodeAsync(connectionId); |
|||
|
|||
_logger.LogInformation("WebSocket connection accepted: {ConnectionId} on node {NodeId}", |
|||
connectionId, _nodeId); |
|||
|
|||
return connectionId; |
|||
} |
|||
|
|||
public async Task<bool> CloseConnectionAsync(string connectionId) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var userKey = $"{USER_PREFIX}{connectionId}"; |
|||
|
|||
// 获取连接信息
|
|||
var connectionJson = await _distributedCache.GetStringAsync(connectionKey); |
|||
if (connectionJson != null) |
|||
{ |
|||
var connection = JsonSerializer.Deserialize<WebSocketConnection>(connectionJson); |
|||
if (connection != null) |
|||
{ |
|||
connection.Close(); |
|||
await _distributedCache.SetStringAsync( |
|||
connectionKey, |
|||
JsonSerializer.Serialize(connection)); |
|||
} |
|||
} |
|||
|
|||
// 从节点连接列表中移除
|
|||
await RemoveConnectionFromNodeAsync(connectionId); |
|||
|
|||
// 清理缓存
|
|||
await _distributedCache.RemoveAsync(connectionKey); |
|||
await _distributedCache.RemoveAsync(userKey); |
|||
|
|||
_logger.LogInformation("WebSocket connection closed: {ConnectionId}", connectionId); |
|||
return true; |
|||
} |
|||
|
|||
public async Task<bool> SendMessageAsync(string connectionId, byte[] message) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var connectionJson = await _distributedCache.GetStringAsync(connectionKey); |
|||
|
|||
if (connectionJson != null) |
|||
{ |
|||
var connection = JsonSerializer.Deserialize<WebSocketConnection>(connectionJson); |
|||
if (connection?.State == WebSocketState.Open) |
|||
{ |
|||
// 这里需要实现消息发送逻辑
|
|||
// 可能需要使用消息队列或其他机制
|
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public async Task<bool> BroadcastMessageAsync(byte[] message) |
|||
{ |
|||
// 获取所有节点的连接列表
|
|||
var nodes = await GetAllNodesAsync(); |
|||
foreach (var node in nodes) |
|||
{ |
|||
// 向每个节点发送广播消息
|
|||
// 这里需要实现节点间的消息传递机制
|
|||
} |
|||
return true; |
|||
} |
|||
|
|||
public async Task<bool> SendMessageToUserAsync(string userId, byte[] message) |
|||
{ |
|||
// 获取用户的所有连接
|
|||
var connections = await GetUserConnectionsAsync(userId); |
|||
foreach (var connection in connections) |
|||
{ |
|||
await SendMessageAsync(connection.ConnectionId, message); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
public async Task AssociateUserAsync(string connectionId, string userId) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var userKey = $"{USER_PREFIX}{connectionId}"; |
|||
|
|||
var connectionJson = await _distributedCache.GetStringAsync(connectionKey); |
|||
if (connectionJson != null) |
|||
{ |
|||
var connection = JsonSerializer.Deserialize<WebSocketConnection>(connectionJson); |
|||
if (connection != null) |
|||
{ |
|||
connection.AssociateUser(userId); |
|||
await _distributedCache.SetStringAsync( |
|||
connectionKey, |
|||
JsonSerializer.Serialize(connection)); |
|||
await _distributedCache.SetStringAsync(userKey, userId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public async Task<WebSocketConnection?> GetConnectionAsync(string connectionId) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var connectionJson = await _distributedCache.GetStringAsync(connectionKey); |
|||
return connectionJson != null |
|||
? JsonSerializer.Deserialize<WebSocketConnection>(connectionJson) |
|||
: null; |
|||
} |
|||
|
|||
public async Task<IEnumerable<WebSocketConnection>> GetUserConnectionsAsync(string userId) |
|||
{ |
|||
var connections = new List<WebSocketConnection>(); |
|||
// 这里需要实现获取用户所有连接的逻辑
|
|||
return connections; |
|||
} |
|||
|
|||
private async Task AddConnectionToNodeAsync(string connectionId) |
|||
{ |
|||
var nodeKey = $"{NODE_PREFIX}{_nodeId}"; |
|||
var connections = await GetNodeConnectionsAsync(); |
|||
connections.Add(connectionId); |
|||
await _distributedCache.SetStringAsync( |
|||
nodeKey, |
|||
JsonSerializer.Serialize(connections)); |
|||
} |
|||
|
|||
private async Task RemoveConnectionFromNodeAsync(string connectionId) |
|||
{ |
|||
var nodeKey = $"{NODE_PREFIX}{_nodeId}"; |
|||
var connections = await GetNodeConnectionsAsync(); |
|||
connections.Remove(connectionId); |
|||
await _distributedCache.SetStringAsync( |
|||
nodeKey, |
|||
JsonSerializer.Serialize(connections)); |
|||
} |
|||
|
|||
private async Task<List<string>> GetNodeConnectionsAsync() |
|||
{ |
|||
var nodeKey = $"{NODE_PREFIX}{_nodeId}"; |
|||
var connectionsJson = await _distributedCache.GetStringAsync(nodeKey); |
|||
return connectionsJson != null |
|||
? JsonSerializer.Deserialize<List<string>>(connectionsJson) ?? new List<string>() |
|||
: new List<string>(); |
|||
} |
|||
|
|||
private async Task<List<string>> GetAllNodesAsync() |
|||
{ |
|||
// 这里需要实现获取所有节点的逻辑
|
|||
// 可能需要使用服务发现或其他机制
|
|||
return new List<string>(); |
|||
} |
|||
} |
@ -1,65 +0,0 @@ |
|||
using System.Diagnostics; |
|||
using System.Diagnostics.Metrics; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CellularManagement.Infrastructure.Monitoring; |
|||
|
|||
public class WebSocketMetrics |
|||
{ |
|||
private readonly ILogger<WebSocketMetrics> _logger; |
|||
private readonly Meter _meter; |
|||
private readonly Counter<int> _connectionsCounter; |
|||
private readonly Counter<int> _messagesCounter; |
|||
private readonly Histogram<double> _messageLatency; |
|||
private readonly Histogram<double> _messageSize; |
|||
private readonly Counter<int> _errorsCounter; |
|||
|
|||
public WebSocketMetrics(ILogger<WebSocketMetrics> logger) |
|||
{ |
|||
_logger = logger; |
|||
_meter = new Meter("CellularManagement.WebSocket", "1.0.0"); |
|||
|
|||
_connectionsCounter = _meter.CreateCounter<int>("websocket.connections", "个", "活跃连接数"); |
|||
_messagesCounter = _meter.CreateCounter<int>("websocket.messages", "个", "消息处理数"); |
|||
_messageLatency = _meter.CreateHistogram<double>("websocket.message.latency", "ms", "消息处理延迟"); |
|||
_messageSize = _meter.CreateHistogram<double>("websocket.message.size", "bytes", "消息大小"); |
|||
_errorsCounter = _meter.CreateCounter<int>("websocket.errors", "个", "错误数"); |
|||
} |
|||
|
|||
public void ConnectionEstablished() |
|||
{ |
|||
_connectionsCounter.Add(1); |
|||
_logger.LogInformation("WebSocket connection established"); |
|||
} |
|||
|
|||
public void ConnectionClosed() |
|||
{ |
|||
_connectionsCounter.Add(-1); |
|||
_logger.LogInformation("WebSocket connection closed"); |
|||
} |
|||
|
|||
public void MessageReceived(int size) |
|||
{ |
|||
_messagesCounter.Add(1); |
|||
_messageSize.Record(size); |
|||
_logger.LogDebug("WebSocket message received, size: {Size} bytes", size); |
|||
} |
|||
|
|||
public void MessageProcessed(TimeSpan latency) |
|||
{ |
|||
_messageLatency.Record(latency.TotalMilliseconds); |
|||
_logger.LogDebug("WebSocket message processed, latency: {Latency}ms", latency.TotalMilliseconds); |
|||
} |
|||
|
|||
public void ErrorOccurred(string errorType) |
|||
{ |
|||
_errorsCounter.Add(1); |
|||
_logger.LogError("WebSocket error occurred: {ErrorType}", errorType); |
|||
} |
|||
|
|||
public void RecordGauge(string name, double value) |
|||
{ |
|||
var gauge = _meter.CreateObservableGauge<double>($"websocket.{name}", () => value); |
|||
_logger.LogDebug("WebSocket gauge recorded: {Name} = {Value}", name, value); |
|||
} |
|||
} |
@ -1,54 +0,0 @@ |
|||
using CellularManagement.Application.Pipeline; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CellularManagement.Infrastructure.Pipeline.Handlers; |
|||
|
|||
public class WebSocketMessageValidationHandler : IWebSocketMessageHandler |
|||
{ |
|||
private readonly ILogger<WebSocketMessageValidationHandler> _logger; |
|||
|
|||
public string HandlerName => "MessageValidationHandler"; |
|||
public int Priority => 100; |
|||
|
|||
public WebSocketMessageValidationHandler(ILogger<WebSocketMessageValidationHandler> logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
public bool CanHandle(string messageType) |
|||
{ |
|||
return true; // 处理所有消息类型
|
|||
} |
|||
|
|||
public async Task HandleAsync(WebSocketMessageContext context, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 验证消息格式
|
|||
if (string.IsNullOrEmpty(context.MessageType)) |
|||
{ |
|||
throw new ArgumentException("Message type cannot be empty"); |
|||
} |
|||
|
|||
if (context.Payload == null || context.Payload.Length == 0) |
|||
{ |
|||
throw new ArgumentException("Message payload cannot be empty"); |
|||
} |
|||
|
|||
// 验证消息大小
|
|||
if (context.Payload.Length > 1024 * 1024) // 1MB
|
|||
{ |
|||
throw new ArgumentException("Message payload too large"); |
|||
} |
|||
|
|||
_logger.LogInformation("Message validation passed for connection: {ConnectionId}", context.ConnectionId); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "Message validation failed for connection: {ConnectionId}", context.ConnectionId); |
|||
context.Error = ex; |
|||
context.IsHandled = true; |
|||
throw; |
|||
} |
|||
} |
|||
} |
@ -1,145 +0,0 @@ |
|||
using System.Threading.Channels; |
|||
using CellularManagement.Application.Pipeline; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Events; |
|||
|
|||
namespace CellularManagement.Infrastructure.Pipeline; |
|||
|
|||
public class WebSocketMessagePipeline : IWebSocketMessagePipeline |
|||
{ |
|||
private readonly Channel<WebSocketMessageContext> _messageChannel; |
|||
private readonly List<IWebSocketMessageHandler> _handlers = new(); |
|||
private readonly ILogger<WebSocketMessagePipeline> _logger; |
|||
private readonly IMediator _mediator; |
|||
private readonly int _batchSize; |
|||
private readonly int _maxQueueSize; |
|||
|
|||
public WebSocketMessagePipeline( |
|||
ILogger<WebSocketMessagePipeline> logger, |
|||
IMediator mediator, |
|||
int batchSize = 100, |
|||
int maxQueueSize = 10000) |
|||
{ |
|||
_logger = logger; |
|||
_mediator = mediator; |
|||
_batchSize = batchSize; |
|||
_maxQueueSize = maxQueueSize; |
|||
|
|||
var options = new BoundedChannelOptions(maxQueueSize) |
|||
{ |
|||
FullMode = BoundedChannelFullMode.Wait, |
|||
SingleWriter = false, |
|||
SingleReader = false |
|||
}; |
|||
|
|||
_messageChannel = Channel.CreateBounded<WebSocketMessageContext>(options); |
|||
} |
|||
|
|||
public async Task ProcessMessageAsync(WebSocketMessageContext context, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
await _messageChannel.Writer.WriteAsync(context, cancellationToken); |
|||
await _mediator.Publish(new WebSocketMessageReceivedEvent( |
|||
context.ConnectionId, |
|||
context.MessageType, |
|||
context.Payload), cancellationToken); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "Failed to process message for connection: {ConnectionId}", context.ConnectionId); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
public void RegisterHandler(IWebSocketMessageHandler handler) |
|||
{ |
|||
_handlers.Add(handler); |
|||
_handlers.Sort((a, b) => b.Priority.CompareTo(a.Priority)); |
|||
} |
|||
|
|||
public void UnregisterHandler(string handlerName) |
|||
{ |
|||
_handlers.RemoveAll(h => h.HandlerName == handlerName); |
|||
} |
|||
|
|||
public async Task StartProcessingAsync(CancellationToken cancellationToken) |
|||
{ |
|||
var batch = new List<WebSocketMessageContext>(_batchSize); |
|||
|
|||
try |
|||
{ |
|||
while (!cancellationToken.IsCancellationRequested) |
|||
{ |
|||
batch.Clear(); |
|||
|
|||
while (batch.Count < _batchSize && |
|||
await _messageChannel.Reader.WaitToReadAsync(cancellationToken)) |
|||
{ |
|||
if (_messageChannel.Reader.TryRead(out var message)) |
|||
{ |
|||
batch.Add(message); |
|||
} |
|||
} |
|||
|
|||
if (batch.Count > 0) |
|||
{ |
|||
await ProcessBatchAsync(batch, cancellationToken); |
|||
} |
|||
} |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
_logger.LogInformation("Message processing pipeline stopped"); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "Error in message processing pipeline"); |
|||
} |
|||
} |
|||
|
|||
private async Task ProcessBatchAsync(List<WebSocketMessageContext> batch, CancellationToken cancellationToken) |
|||
{ |
|||
foreach (var message in batch) |
|||
{ |
|||
try |
|||
{ |
|||
await ProcessMessageWithHandlersAsync(message, cancellationToken); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "Error processing message: {MessageId}", message.ConnectionId); |
|||
message.Error = ex; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task ProcessMessageWithHandlersAsync(WebSocketMessageContext context, CancellationToken cancellationToken) |
|||
{ |
|||
foreach (var handler in _handlers) |
|||
{ |
|||
if (handler.CanHandle(context.MessageType) && !context.IsHandled) |
|||
{ |
|||
try |
|||
{ |
|||
await handler.HandleAsync(context, cancellationToken); |
|||
if (context.IsHandled) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "Handler {HandlerName} failed to process message", handler.HandlerName); |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!context.IsHandled) |
|||
{ |
|||
_logger.LogWarning("No handler found for message type: {MessageType}", context.MessageType); |
|||
} |
|||
} |
|||
} |
@ -1,59 +0,0 @@ |
|||
using System.Buffers; |
|||
using System.Collections.Concurrent; |
|||
using CellularManagement.Application.Pipeline; |
|||
|
|||
namespace CellularManagement.Infrastructure.Pooling; |
|||
|
|||
public class WebSocketMessagePool |
|||
{ |
|||
private readonly ConcurrentBag<WebSocketMessageContext> _pool = new(); |
|||
private readonly ArrayPool<byte> _arrayPool = ArrayPool<byte>.Shared; |
|||
private readonly int _maxPoolSize; |
|||
private readonly int _bufferSize; |
|||
|
|||
public WebSocketMessagePool(int maxPoolSize = 1000, int bufferSize = 1024 * 4) |
|||
{ |
|||
_maxPoolSize = maxPoolSize; |
|||
_bufferSize = bufferSize; |
|||
} |
|||
|
|||
public WebSocketMessageContext Rent() |
|||
{ |
|||
if (_pool.TryTake(out var context)) |
|||
{ |
|||
return context; |
|||
} |
|||
|
|||
return new WebSocketMessageContext |
|||
{ |
|||
Payload = _arrayPool.Rent(_bufferSize) |
|||
}; |
|||
} |
|||
|
|||
public void Return(WebSocketMessageContext context) |
|||
{ |
|||
if (_pool.Count < _maxPoolSize) |
|||
{ |
|||
context.ConnectionId = string.Empty; |
|||
context.MessageType = string.Empty; |
|||
context.Properties.Clear(); |
|||
context.IsHandled = false; |
|||
context.Error = null; |
|||
|
|||
if (context.Payload != null) |
|||
{ |
|||
_arrayPool.Return(context.Payload); |
|||
context.Payload = _arrayPool.Rent(_bufferSize); |
|||
} |
|||
|
|||
_pool.Add(context); |
|||
} |
|||
else |
|||
{ |
|||
if (context.Payload != null) |
|||
{ |
|||
_arrayPool.Return(context.Payload); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,129 +0,0 @@ |
|||
using System.Net.WebSockets; |
|||
using System.Collections.Concurrent; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Application.Services; |
|||
|
|||
namespace CellularManagement.Infrastructure.WebSocket; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket连接管理器
|
|||
/// 负责管理WebSocket连接的创建、删除和查询
|
|||
/// 使用线程安全的并发集合存储连接信息
|
|||
/// </summary>
|
|||
public class WebSocketConnectionManager |
|||
{ |
|||
/// <summary>
|
|||
/// 日志记录器,用于记录连接管理器的操作日志
|
|||
/// </summary>
|
|||
private readonly ILogger<WebSocketConnectionManager> _logger; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket连接字典
|
|||
/// 键为连接ID,值为WebSocket实例
|
|||
/// 使用线程安全的ConcurrentDictionary
|
|||
/// </summary>
|
|||
private readonly ConcurrentDictionary<string, System.Net.WebSockets.WebSocket> _sockets = new(); |
|||
|
|||
/// <summary>
|
|||
/// 用户连接关联字典
|
|||
/// 键为连接ID,值为用户ID
|
|||
/// 使用线程安全的ConcurrentDictionary
|
|||
/// </summary>
|
|||
private readonly ConcurrentDictionary<string, string> _userConnections = new(); |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="logger">日志记录器,用于记录连接管理器的操作日志</param>
|
|||
public WebSocketConnectionManager(ILogger<WebSocketConnectionManager> logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 添加新的WebSocket连接
|
|||
/// 生成唯一的连接ID并存储连接信息
|
|||
/// </summary>
|
|||
/// <param name="socket">要添加的WebSocket实例</param>
|
|||
/// <returns>新创建的连接ID</returns>
|
|||
public string AddConnection(System.Net.WebSockets.WebSocket socket) |
|||
{ |
|||
var connectionId = Guid.NewGuid().ToString(); |
|||
_sockets.TryAdd(connectionId, socket); |
|||
_logger.LogInformation("WebSocket连接已添加: {ConnectionId}", connectionId); |
|||
return connectionId; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 移除WebSocket连接
|
|||
/// 清理连接相关的所有信息
|
|||
/// </summary>
|
|||
/// <param name="connectionId">要移除的连接ID</param>
|
|||
/// <returns>是否成功移除连接</returns>
|
|||
public bool RemoveConnection(string connectionId) |
|||
{ |
|||
_sockets.TryRemove(connectionId, out _); |
|||
_userConnections.TryRemove(connectionId, out _); |
|||
_logger.LogInformation("WebSocket连接已移除: {ConnectionId}", connectionId); |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定ID的WebSocket连接
|
|||
/// </summary>
|
|||
/// <param name="connectionId">要查询的连接ID</param>
|
|||
/// <returns>WebSocket实例,如果不存在则返回null</returns>
|
|||
public System.Net.WebSockets.WebSocket? GetConnection(string connectionId) |
|||
{ |
|||
_sockets.TryGetValue(connectionId, out var socket); |
|||
return socket; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有WebSocket连接
|
|||
/// </summary>
|
|||
/// <returns>所有WebSocket实例的集合</returns>
|
|||
public IEnumerable<System.Net.WebSockets.WebSocket> GetAllConnections() |
|||
{ |
|||
return _sockets.Values; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将连接与用户关联
|
|||
/// 建立连接ID和用户ID的映射关系
|
|||
/// </summary>
|
|||
/// <param name="connectionId">要关联的连接ID</param>
|
|||
/// <param name="userId">要关联的用户ID</param>
|
|||
public void AssociateUser(string connectionId, string userId) |
|||
{ |
|||
_userConnections.TryAdd(connectionId, userId); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取连接关联的用户ID
|
|||
/// </summary>
|
|||
/// <param name="connectionId">要查询的连接ID</param>
|
|||
/// <returns>用户ID,如果未关联则返回null</returns>
|
|||
public string? GetUserId(string connectionId) |
|||
{ |
|||
_userConnections.TryGetValue(connectionId, out var userId); |
|||
return userId; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取用户的所有WebSocket连接
|
|||
/// 根据用户ID查找所有关联的连接
|
|||
/// </summary>
|
|||
/// <param name="userId">要查询的用户ID</param>
|
|||
/// <returns>用户的所有WebSocket实例的集合</returns>
|
|||
public IEnumerable<System.Net.WebSockets.WebSocket> GetUserConnections(string userId) |
|||
{ |
|||
var connections = _userConnections |
|||
.Where(kvp => kvp.Value == userId) |
|||
.Select(kvp => kvp.Key) |
|||
.Select(connectionId => GetConnection(connectionId)) |
|||
.Where(socket => socket != null); |
|||
|
|||
return connections!; |
|||
} |
|||
} |
@ -1,199 +0,0 @@ |
|||
using System.Threading.Channels; |
|||
using System.Buffers; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CellularManagement.Infrastructure.WebSocket; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket消息处理管道
|
|||
/// 负责批量处理和分发WebSocket消息
|
|||
/// 使用Channel实现消息队列,支持批量处理提高性能
|
|||
/// </summary>
|
|||
public class WebSocketMessagePipeline |
|||
{ |
|||
/// <summary>
|
|||
/// 消息通道,用于存储待处理的消息
|
|||
/// 使用有界通道限制内存使用
|
|||
/// </summary>
|
|||
private readonly Channel<WebSocketMessage> _messageChannel; |
|||
|
|||
/// <summary>
|
|||
/// 日志记录器,用于记录管道的操作日志
|
|||
/// </summary>
|
|||
private readonly ILogger<WebSocketMessagePipeline> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 内存池,用于复用内存缓冲区
|
|||
/// </summary>
|
|||
private readonly MemoryPool<byte> _memoryPool; |
|||
|
|||
/// <summary>
|
|||
/// 批处理大小,每次处理的消息数量
|
|||
/// </summary>
|
|||
private readonly int _batchSize; |
|||
|
|||
/// <summary>
|
|||
/// 最大队列大小,限制通道中可存储的消息数量
|
|||
/// </summary>
|
|||
private readonly int _maxQueueSize; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="logger">日志记录器,用于记录管道的操作日志</param>
|
|||
/// <param name="batchSize">批处理大小,每次处理的消息数量,默认100</param>
|
|||
/// <param name="maxQueueSize">最大队列大小,限制通道中可存储的消息数量,默认10000</param>
|
|||
public WebSocketMessagePipeline( |
|||
ILogger<WebSocketMessagePipeline> logger, |
|||
int batchSize = 100, |
|||
int maxQueueSize = 10000) |
|||
{ |
|||
_logger = logger; |
|||
_batchSize = batchSize; |
|||
_maxQueueSize = maxQueueSize; |
|||
_memoryPool = MemoryPool<byte>.Shared; |
|||
|
|||
// 创建有界通道
|
|||
var options = new BoundedChannelOptions(maxQueueSize) |
|||
{ |
|||
FullMode = BoundedChannelFullMode.Wait, // 队列满时等待
|
|||
SingleWriter = false, // 允许多个写入者
|
|||
SingleReader = false // 允许多个读取者
|
|||
}; |
|||
|
|||
_messageChannel = Channel.CreateBounded<WebSocketMessage>(options); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 写入消息到管道
|
|||
/// 将消息添加到通道中等待处理
|
|||
/// </summary>
|
|||
/// <param name="message">要处理的WebSocket消息</param>
|
|||
/// <returns>是否成功写入消息</returns>
|
|||
public async ValueTask<bool> WriteMessageAsync(WebSocketMessage message) |
|||
{ |
|||
try |
|||
{ |
|||
await _messageChannel.Writer.WriteAsync(message); |
|||
return true; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "写入消息到管道失败"); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理消息
|
|||
/// 循环从通道中读取消息并批量处理
|
|||
/// </summary>
|
|||
/// <param name="cancellationToken">取消令牌,用于停止处理</param>
|
|||
public async Task ProcessMessagesAsync(CancellationToken cancellationToken) |
|||
{ |
|||
var batch = new List<WebSocketMessage>(_batchSize); |
|||
|
|||
try |
|||
{ |
|||
while (!cancellationToken.IsCancellationRequested) |
|||
{ |
|||
batch.Clear(); |
|||
|
|||
// 批量读取消息
|
|||
while (batch.Count < _batchSize && |
|||
await _messageChannel.Reader.WaitToReadAsync(cancellationToken)) |
|||
{ |
|||
if (_messageChannel.Reader.TryRead(out var message)) |
|||
{ |
|||
batch.Add(message); |
|||
} |
|||
} |
|||
|
|||
if (batch.Count > 0) |
|||
{ |
|||
await ProcessBatchAsync(batch, cancellationToken); |
|||
} |
|||
} |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
_logger.LogInformation("消息处理管道已停止"); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "消息处理管道出错"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 批量处理消息
|
|||
/// 处理一批消息,逐个处理并记录错误
|
|||
/// </summary>
|
|||
/// <param name="batch">要处理的消息批次</param>
|
|||
/// <param name="cancellationToken">取消令牌,用于停止处理</param>
|
|||
private async Task ProcessBatchAsync(List<WebSocketMessage> batch, CancellationToken cancellationToken) |
|||
{ |
|||
// 这里实现具体的消息处理逻辑
|
|||
foreach (var message in batch) |
|||
{ |
|||
try |
|||
{ |
|||
// 处理消息
|
|||
await ProcessMessageAsync(message, cancellationToken); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "处理消息时出错: {MessageId}", message.MessageId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理单个消息
|
|||
/// 实现具体的消息处理逻辑
|
|||
/// </summary>
|
|||
/// <param name="message">要处理的WebSocket消息</param>
|
|||
/// <param name="cancellationToken">取消令牌,用于停止处理</param>
|
|||
private async Task ProcessMessageAsync(WebSocketMessage message, CancellationToken cancellationToken) |
|||
{ |
|||
// 实现具体的消息处理逻辑
|
|||
// 这里可以添加消息验证、转换、业务处理等步骤
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// WebSocket消息实体
|
|||
/// 表示一个WebSocket消息的基本信息
|
|||
/// </summary>
|
|||
public class WebSocketMessage |
|||
{ |
|||
/// <summary>
|
|||
/// 消息ID
|
|||
/// 唯一标识一条消息
|
|||
/// </summary>
|
|||
public string MessageId { get; set; } = Guid.NewGuid().ToString(); |
|||
|
|||
/// <summary>
|
|||
/// 连接ID
|
|||
/// 标识消息来自哪个WebSocket连接
|
|||
/// </summary>
|
|||
public string ConnectionId { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 消息类型
|
|||
/// 用于标识消息的类型,可用于路由和处理
|
|||
/// </summary>
|
|||
public string MessageType { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 消息内容
|
|||
/// 消息的实际数据
|
|||
/// </summary>
|
|||
public byte[] Payload { get; set; } = Array.Empty<byte>(); |
|||
|
|||
/// <summary>
|
|||
/// 时间戳
|
|||
/// 消息创建的时间
|
|||
/// </summary>
|
|||
public DateTime Timestamp { get; set; } = DateTime.UtcNow; |
|||
} |
@ -1,72 +0,0 @@ |
|||
using System.Collections.Concurrent; |
|||
using CellularManagement.Application.Pipeline; |
|||
|
|||
namespace CellularManagement.Infrastructure.WebSocket; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket消息池
|
|||
/// 负责管理WebSocket消息上下文的复用,减少内存分配
|
|||
/// 使用对象池模式提高性能
|
|||
/// </summary>
|
|||
public class WebSocketMessagePool |
|||
{ |
|||
/// <summary>
|
|||
/// 消息上下文池
|
|||
/// 使用线程安全的ConcurrentBag存储可复用的消息上下文
|
|||
/// </summary>
|
|||
private readonly ConcurrentBag<WebSocketMessageContext> _pool = new(); |
|||
|
|||
/// <summary>
|
|||
/// 最大池大小
|
|||
/// 限制池中可存储的消息上下文数量
|
|||
/// </summary>
|
|||
private readonly int _maxPoolSize; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="maxPoolSize">最大池大小,限制池中可存储的消息上下文数量,默认1000</param>
|
|||
public WebSocketMessagePool(int maxPoolSize = 1000) |
|||
{ |
|||
_maxPoolSize = maxPoolSize; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 从池中获取消息上下文
|
|||
/// 如果池中有可用的上下文则复用,否则创建新的
|
|||
/// </summary>
|
|||
/// <returns>可用的消息上下文</returns>
|
|||
public WebSocketMessageContext Rent() |
|||
{ |
|||
// 尝试从池中获取可用的上下文
|
|||
if (_pool.TryTake(out var context)) |
|||
{ |
|||
return context; |
|||
} |
|||
|
|||
// 如果池中没有可用的上下文,创建新的
|
|||
return new WebSocketMessageContext(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将消息上下文返回到池中
|
|||
/// 重置上下文状态并放回池中,如果池已满则丢弃
|
|||
/// </summary>
|
|||
/// <param name="context">要返回的消息上下文</param>
|
|||
public void Return(WebSocketMessageContext context) |
|||
{ |
|||
// 如果池未满,重置上下文并放回池中
|
|||
if (_pool.Count < _maxPoolSize) |
|||
{ |
|||
// 重置上下文状态
|
|||
context.ConnectionId = string.Empty; |
|||
context.MessageType = string.Empty; |
|||
context.Payload = Array.Empty<byte>(); |
|||
context.Properties.Clear(); |
|||
context.IsHandled = false; |
|||
context.Error = null; |
|||
|
|||
_pool.Add(context); |
|||
} |
|||
} |
|||
} |
@ -1,134 +0,0 @@ |
|||
using System.Net.WebSockets; |
|||
using System.Text; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using CellularManagement.Application.Services; |
|||
|
|||
namespace CellularManagement.Infrastructure.WebSocket; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket中间件
|
|||
/// 负责处理WebSocket请求的生命周期和消息处理
|
|||
/// 作为ASP.NET Core中间件,拦截WebSocket请求并管理连接
|
|||
/// </summary>
|
|||
public class WebSocketMiddleware |
|||
{ |
|||
/// <summary>
|
|||
/// 请求委托,用于继续处理管道中的下一个中间件
|
|||
/// </summary>
|
|||
private readonly RequestDelegate _next; |
|||
|
|||
/// <summary>
|
|||
/// 日志记录器,用于记录中间件的操作日志
|
|||
/// </summary>
|
|||
private readonly ILogger<WebSocketMiddleware> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 服务提供者,用于创建服务作用域和获取所需服务
|
|||
/// </summary>
|
|||
private readonly IServiceProvider _serviceProvider; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="next">请求委托,用于继续处理管道中的下一个中间件</param>
|
|||
/// <param name="logger">日志记录器,用于记录中间件的操作日志</param>
|
|||
/// <param name="serviceProvider">服务提供者,用于创建服务作用域和获取所需服务</param>
|
|||
public WebSocketMiddleware( |
|||
RequestDelegate next, |
|||
ILogger<WebSocketMiddleware> logger, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
_next = next; |
|||
_logger = logger; |
|||
_serviceProvider = serviceProvider; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理HTTP请求
|
|||
/// 检查是否为WebSocket请求,如果是则处理WebSocket连接
|
|||
/// </summary>
|
|||
/// <param name="context">HTTP上下文,包含请求和响应信息</param>
|
|||
public async Task InvokeAsync(HttpContext context) |
|||
{ |
|||
// 检查是否为WebSocket请求
|
|||
if (context.WebSockets.IsWebSocketRequest) |
|||
{ |
|||
// 创建服务作用域,确保服务实例的生命周期正确
|
|||
using var scope = _serviceProvider.CreateScope(); |
|||
var webSocketService = scope.ServiceProvider.GetRequiredService<IWebSocketService>(); |
|||
|
|||
// 接受WebSocket连接
|
|||
var webSocket = await context.WebSockets.AcceptWebSocketAsync(); |
|||
var connectionId = await webSocketService.AcceptConnectionAsync(webSocket); |
|||
|
|||
try |
|||
{ |
|||
// 处理WebSocket连接
|
|||
await HandleWebSocketConnection(webSocket, connectionId, webSocketService); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "处理WebSocket连接时出错: {ConnectionId}", connectionId); |
|||
} |
|||
finally |
|||
{ |
|||
// 确保连接被正确关闭
|
|||
await webSocketService.CloseConnectionAsync(connectionId); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// 如果不是WebSocket请求,继续处理管道
|
|||
await _next(context); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理WebSocket连接
|
|||
/// 循环接收消息并处理,直到连接关闭
|
|||
/// </summary>
|
|||
/// <param name="webSocket">WebSocket实例,用于接收和发送消息</param>
|
|||
/// <param name="connectionId">连接ID,用于标识连接</param>
|
|||
/// <param name="webSocketService">WebSocket服务,用于处理消息</param>
|
|||
private async Task HandleWebSocketConnection( |
|||
System.Net.WebSockets.WebSocket webSocket, |
|||
string connectionId, |
|||
IWebSocketService webSocketService) |
|||
{ |
|||
// 创建接收缓冲区,大小为4KB
|
|||
var buffer = new byte[1024 * 4]; |
|||
var receiveResult = await webSocket.ReceiveAsync( |
|||
new ArraySegment<byte>(buffer), CancellationToken.None); |
|||
|
|||
// 循环接收消息,直到收到关闭消息
|
|||
while (!receiveResult.CloseStatus.HasValue) |
|||
{ |
|||
try |
|||
{ |
|||
// 处理接收到的消息
|
|||
var message = buffer.Take(receiveResult.Count).ToArray(); |
|||
await webSocketService.SendMessageAsync(connectionId, message); |
|||
|
|||
// 继续接收下一条消息
|
|||
receiveResult = await webSocket.ReceiveAsync( |
|||
new ArraySegment<byte>(buffer), CancellationToken.None); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "处理WebSocket消息时出错: {ConnectionId}", connectionId); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// 如果收到关闭消息,则关闭连接
|
|||
if (receiveResult.CloseStatus.HasValue) |
|||
{ |
|||
await webSocket.CloseAsync( |
|||
receiveResult.CloseStatus.Value, |
|||
receiveResult.CloseStatusDescription, |
|||
CancellationToken.None); |
|||
} |
|||
} |
|||
} |
@ -1,420 +0,0 @@ |
|||
using System.Net.WebSockets; |
|||
using System.Text; |
|||
using System.Text.Json; |
|||
using CellularManagement.Application.Services; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Infrastructure.Monitoring; |
|||
using CellularManagement.Infrastructure.Pooling; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
|
|||
namespace CellularManagement.Infrastructure.WebSocket; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket服务实现类
|
|||
/// 负责管理WebSocket连接的生命周期、消息发送和接收
|
|||
/// 使用分布式缓存存储连接信息,本地缓存存储WebSocket实例
|
|||
/// </summary>
|
|||
public class WebSocketService : IWebSocketService |
|||
{ |
|||
/// <summary>
|
|||
/// 日志记录器,用于记录WebSocket服务的操作日志
|
|||
/// </summary>
|
|||
private readonly ILogger<WebSocketService> _logger; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket指标监控,用于记录连接、消息等指标
|
|||
/// </summary>
|
|||
private readonly WebSocketMetrics _metrics; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket消息池,用于复用消息上下文对象
|
|||
/// </summary>
|
|||
private readonly WebSocketMessagePool _messagePool; |
|||
|
|||
/// <summary>
|
|||
/// 分布式缓存,用于存储连接信息
|
|||
/// </summary>
|
|||
private readonly IDistributedCache _distributedCache; |
|||
|
|||
/// <summary>
|
|||
/// 本地缓存服务,用于存储WebSocket实例
|
|||
/// </summary>
|
|||
private readonly ICacheService _cacheService; |
|||
|
|||
/// <summary>
|
|||
/// 当前节点ID,用于标识服务实例
|
|||
/// </summary>
|
|||
private readonly string _nodeId; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket连接信息在缓存中的键前缀
|
|||
/// </summary>
|
|||
private const string CONNECTION_PREFIX = "ws_connection_"; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket实例在缓存中的键前缀
|
|||
/// </summary>
|
|||
private const string WEBSOCKET_PREFIX = "ws_socket_"; |
|||
|
|||
/// <summary>
|
|||
/// 用户关联信息在缓存中的键前缀
|
|||
/// </summary>
|
|||
private const string USER_PREFIX = "ws_user_"; |
|||
|
|||
/// <summary>
|
|||
/// 节点信息在缓存中的键前缀
|
|||
/// </summary>
|
|||
private const string NODE_PREFIX = "ws_node_"; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="logger">日志记录器,用于记录服务操作日志</param>
|
|||
/// <param name="metrics">WebSocket指标监控,用于记录连接和消息指标</param>
|
|||
/// <param name="messagePool">消息池,用于复用消息上下文对象</param>
|
|||
/// <param name="distributedCache">分布式缓存,用于存储连接信息</param>
|
|||
/// <param name="cacheService">本地缓存服务,用于存储WebSocket实例</param>
|
|||
public WebSocketService( |
|||
ILogger<WebSocketService> logger, |
|||
WebSocketMetrics metrics, |
|||
WebSocketMessagePool messagePool, |
|||
IDistributedCache distributedCache, |
|||
ICacheService cacheService) |
|||
{ |
|||
_logger = logger; |
|||
_metrics = metrics; |
|||
_messagePool = messagePool; |
|||
_distributedCache = distributedCache; |
|||
_cacheService = cacheService; |
|||
_nodeId = Guid.NewGuid().ToString(); // 生成唯一的节点ID
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 接受新的WebSocket连接
|
|||
/// 创建连接信息并存储到缓存中
|
|||
/// </summary>
|
|||
/// <param name="webSocket">WebSocket实例</param>
|
|||
/// <returns>新创建的连接ID</returns>
|
|||
public async Task<string> AcceptConnectionAsync(System.Net.WebSockets.WebSocket webSocket) |
|||
{ |
|||
var connectionId = Guid.NewGuid().ToString(); |
|||
var connection = WebSocketConnection.Create(connectionId); |
|||
|
|||
// 生成缓存键
|
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var webSocketKey = $"{WEBSOCKET_PREFIX}{connectionId}"; |
|||
var nodeKey = $"{NODE_PREFIX}{_nodeId}"; |
|||
|
|||
// 将连接信息存储到分布式缓存
|
|||
await _distributedCache.SetStringAsync( |
|||
connectionKey, |
|||
JsonSerializer.Serialize(connection), |
|||
new DistributedCacheEntryOptions |
|||
{ |
|||
SlidingExpiration = TimeSpan.FromMinutes(30) // 设置30分钟滑动过期
|
|||
}); |
|||
|
|||
// 将WebSocket实例存储到本地缓存
|
|||
_cacheService.Set(webSocketKey, webSocket, new MemoryCacheEntryOptions |
|||
{ |
|||
SlidingExpiration = TimeSpan.FromMinutes(30) |
|||
}); |
|||
|
|||
// 将连接添加到当前节点
|
|||
await AddConnectionToNodeAsync(connectionId); |
|||
_metrics.ConnectionEstablished(); // 记录连接建立指标
|
|||
_logger.LogInformation("WebSocket连接已建立: {ConnectionId} 在节点 {NodeId}", |
|||
connectionId, _nodeId); |
|||
|
|||
return connectionId; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 关闭WebSocket连接
|
|||
/// 清理连接相关的所有缓存信息
|
|||
/// </summary>
|
|||
/// <param name="connectionId">要关闭的连接ID</param>
|
|||
/// <returns>是否成功关闭连接</returns>
|
|||
public async Task<bool> CloseConnectionAsync(string connectionId) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var webSocketKey = $"{WEBSOCKET_PREFIX}{connectionId}"; |
|||
var userKey = $"{USER_PREFIX}{connectionId}"; |
|||
|
|||
// 获取连接信息
|
|||
var connectionJson = await _distributedCache.GetStringAsync(connectionKey); |
|||
if (_cacheService.TryGetValue(webSocketKey, out System.Net.WebSockets.WebSocket? webSocket)) |
|||
{ |
|||
try |
|||
{ |
|||
if (connectionJson != null) |
|||
{ |
|||
var connection = JsonSerializer.Deserialize<WebSocketConnection>(connectionJson); |
|||
if (connection != null && webSocket != null) |
|||
{ |
|||
try |
|||
{ |
|||
// 如果WebSocket处于打开状态,则正常关闭
|
|||
if (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseReceived) |
|||
{ |
|||
await webSocket.CloseAsync( |
|||
WebSocketCloseStatus.NormalClosure, |
|||
"服务器关闭连接", |
|||
CancellationToken.None); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "关闭WebSocket连接时出错: {ConnectionId}", connectionId); |
|||
} |
|||
|
|||
connection.Close(); |
|||
await _distributedCache.SetStringAsync(connectionKey, JsonSerializer.Serialize(connection)); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogWarning(ex, "反序列化WebSocketConnection失败: {ConnectionId}, 继续清理", connectionId); |
|||
} |
|||
} |
|||
|
|||
// 清理连接相关的所有缓存
|
|||
await RemoveConnectionFromNodeAsync(connectionId); |
|||
await _distributedCache.RemoveAsync(connectionKey); |
|||
_cacheService.Remove(webSocketKey); |
|||
await _distributedCache.RemoveAsync(userKey); |
|||
|
|||
_metrics.ConnectionClosed(); // 记录连接关闭指标
|
|||
_logger.LogInformation("WebSocket连接已关闭: {ConnectionId}", connectionId); |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 向指定连接发送消息
|
|||
/// </summary>
|
|||
/// <param name="connectionId">目标连接ID</param>
|
|||
/// <param name="message">要发送的消息内容</param>
|
|||
/// <returns>是否成功发送消息</returns>
|
|||
public async Task<bool> SendMessageAsync(string connectionId, byte[] message) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var webSocketKey = $"{WEBSOCKET_PREFIX}{connectionId}"; |
|||
|
|||
var connectionJson = await _distributedCache.GetStringAsync(connectionKey); |
|||
if (_cacheService.TryGetValue(webSocketKey, out System.Net.WebSockets.WebSocket? webSocket)) |
|||
{ |
|||
try |
|||
{ |
|||
if (connectionJson != null) |
|||
{ |
|||
var connection = JsonSerializer.Deserialize<WebSocketConnection>(connectionJson); |
|||
if (connection?.State == WebSocketState.Open && webSocket != null) |
|||
{ |
|||
try |
|||
{ |
|||
await webSocket.SendAsync( |
|||
new ArraySegment<byte>(message), |
|||
WebSocketMessageType.Text, |
|||
true, |
|||
CancellationToken.None); |
|||
|
|||
_metrics.MessageProcessed(TimeSpan.Zero); // 记录消息处理指标
|
|||
return true; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "发送消息到连接时出错: {ConnectionId}", connectionId); |
|||
_metrics.ErrorOccurred("SendMessage"); // 记录错误指标
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogWarning(ex, "反序列化WebSocketConnection失败: {ConnectionId}, 跳过消息发送", connectionId); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 广播消息到所有连接
|
|||
/// 遍历所有节点上的所有连接并发送消息
|
|||
/// </summary>
|
|||
/// <param name="message">要广播的消息内容</param>
|
|||
/// <returns>是否所有消息都发送成功</returns>
|
|||
public async Task<bool> BroadcastMessageAsync(byte[] message) |
|||
{ |
|||
var nodes = await GetAllNodesAsync(); |
|||
var success = true; |
|||
|
|||
// 遍历所有节点上的所有连接
|
|||
foreach (var node in nodes) |
|||
{ |
|||
var nodeKey = $"{NODE_PREFIX}{node}"; |
|||
var connectionsJson = await _distributedCache.GetStringAsync(nodeKey); |
|||
if (connectionsJson != null) |
|||
{ |
|||
var connections = JsonSerializer.Deserialize<List<string>>(connectionsJson); |
|||
foreach (var connectionId in connections) |
|||
{ |
|||
if (!await SendMessageAsync(connectionId, message)) |
|||
{ |
|||
success = false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return success; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 向指定用户的所有连接发送消息
|
|||
/// </summary>
|
|||
/// <param name="userId">目标用户ID</param>
|
|||
/// <param name="message">要发送的消息内容</param>
|
|||
/// <returns>是否所有消息都发送成功</returns>
|
|||
public async Task<bool> SendMessageToUserAsync(string userId, byte[] message) |
|||
{ |
|||
var userConnections = await GetUserConnectionsAsync(userId); |
|||
var success = true; |
|||
|
|||
// 向用户的所有连接发送消息
|
|||
foreach (var connection in userConnections) |
|||
{ |
|||
if (!await SendMessageAsync(connection.ConnectionId, message)) |
|||
{ |
|||
success = false; |
|||
} |
|||
} |
|||
|
|||
return success; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将连接与用户关联
|
|||
/// 更新连接信息和用户关联缓存
|
|||
/// </summary>
|
|||
/// <param name="connectionId">连接ID</param>
|
|||
/// <param name="userId">用户ID</param>
|
|||
public async Task AssociateUserAsync(string connectionId, string userId) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var userKey = $"{USER_PREFIX}{connectionId}"; |
|||
|
|||
var connectionJson = await _distributedCache.GetStringAsync(connectionKey); |
|||
if (connectionJson != null) |
|||
{ |
|||
var connection = JsonSerializer.Deserialize<WebSocketConnection>(connectionJson); |
|||
if (connection != null) |
|||
{ |
|||
connection.AssociateUser(userId); |
|||
await _distributedCache.SetStringAsync(connectionKey, JsonSerializer.Serialize(connection)); |
|||
await _distributedCache.SetStringAsync(userKey, userId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定连接的信息
|
|||
/// </summary>
|
|||
/// <param name="connectionId">连接ID</param>
|
|||
/// <returns>连接信息,如果不存在则返回null</returns>
|
|||
public async Task<WebSocketConnection?> GetConnectionAsync(string connectionId) |
|||
{ |
|||
var connectionKey = $"{CONNECTION_PREFIX}{connectionId}"; |
|||
var connectionJson = await _distributedCache.GetStringAsync(connectionKey); |
|||
return connectionJson != null |
|||
? JsonSerializer.Deserialize<WebSocketConnection>(connectionJson) |
|||
: null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定用户的所有连接
|
|||
/// </summary>
|
|||
/// <param name="userId">用户ID</param>
|
|||
/// <returns>用户的所有连接信息</returns>
|
|||
public async Task<IEnumerable<WebSocketConnection>> GetUserConnectionsAsync(string userId) |
|||
{ |
|||
var connections = new List<WebSocketConnection>(); |
|||
var nodes = await GetAllNodesAsync(); |
|||
|
|||
// 遍历所有节点,查找用户的连接
|
|||
foreach (var node in nodes) |
|||
{ |
|||
var nodeKey = $"{NODE_PREFIX}{node}"; |
|||
var connectionsJson = await _distributedCache.GetStringAsync(nodeKey); |
|||
if (connectionsJson != null) |
|||
{ |
|||
var connectionIds = JsonSerializer.Deserialize<List<string>>(connectionsJson); |
|||
foreach (var connectionId in connectionIds) |
|||
{ |
|||
var connection = await GetConnectionAsync(connectionId); |
|||
if (connection?.UserId == userId) |
|||
{ |
|||
connections.Add(connection); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return connections; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将连接添加到当前节点
|
|||
/// </summary>
|
|||
/// <param name="connectionId">要添加的连接ID</param>
|
|||
private async Task AddConnectionToNodeAsync(string connectionId) |
|||
{ |
|||
var nodeKey = $"{NODE_PREFIX}{_nodeId}"; |
|||
var connections = await GetNodeConnectionsAsync(); |
|||
connections.Add(connectionId); |
|||
await _distributedCache.SetStringAsync( |
|||
nodeKey, |
|||
JsonSerializer.Serialize(connections)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 从当前节点移除连接
|
|||
/// </summary>
|
|||
/// <param name="connectionId">要移除的连接ID</param>
|
|||
private async Task RemoveConnectionFromNodeAsync(string connectionId) |
|||
{ |
|||
var nodeKey = $"{NODE_PREFIX}{_nodeId}"; |
|||
var connections = await GetNodeConnectionsAsync(); |
|||
connections.Remove(connectionId); |
|||
await _distributedCache.SetStringAsync( |
|||
nodeKey, |
|||
JsonSerializer.Serialize(connections)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取当前节点的所有连接
|
|||
/// </summary>
|
|||
/// <returns>当前节点的所有连接ID列表</returns>
|
|||
private async Task<List<string>> GetNodeConnectionsAsync() |
|||
{ |
|||
var nodeKey = $"{NODE_PREFIX}{_nodeId}"; |
|||
var connectionsJson = await _distributedCache.GetStringAsync(nodeKey); |
|||
return connectionsJson != null |
|||
? JsonSerializer.Deserialize<List<string>>(connectionsJson) ?? new List<string>() |
|||
: new List<string>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有节点ID
|
|||
/// 目前仅返回当前节点ID,后续需要实现服务发现机制
|
|||
/// </summary>
|
|||
/// <returns>所有节点ID列表</returns>
|
|||
private async Task<List<string>> GetAllNodesAsync() |
|||
{ |
|||
// 这里需要实现服务发现机制
|
|||
// 可以使用Redis的Pub/Sub或其他服务发现机制
|
|||
return new List<string> { _nodeId }; |
|||
} |
|||
} |
Loading…
Reference in new issue