5 changed files with 805 additions and 78 deletions
@ -0,0 +1,164 @@ |
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace CellularManagement.WebSocket.Buffer; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket 消息缓冲区
|
|||
/// 用于高效处理 WebSocket 消息的缓冲区实现
|
|||
/// </summary>
|
|||
public sealed class WebSocketMessageBuffer : IDisposable |
|||
{ |
|||
private readonly byte[] _buffer; |
|||
private int _position; |
|||
private readonly int _maxSize; |
|||
private bool _isDisposed; |
|||
private const int MinBufferSize = 1024; // 最小缓冲区大小 1KB
|
|||
private const int MaxBufferSize = 1024 * 1024 * 10; // 最大缓冲区大小 10MB
|
|||
|
|||
/// <summary>
|
|||
/// 当前缓冲区大小
|
|||
/// </summary>
|
|||
public int Size => _position; |
|||
|
|||
/// <summary>
|
|||
/// 缓冲区是否已满
|
|||
/// </summary>
|
|||
public bool IsFull => _position >= _maxSize; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="maxSize">最大缓冲区大小</param>
|
|||
/// <exception cref="ArgumentOutOfRangeException">当 maxSize 超出允许范围时抛出</exception>
|
|||
public WebSocketMessageBuffer(int maxSize) |
|||
{ |
|||
if (maxSize < MinBufferSize || maxSize > MaxBufferSize) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(maxSize), |
|||
$"缓冲区大小必须在 {MinBufferSize} 到 {MaxBufferSize} 字节之间"); |
|||
} |
|||
|
|||
_maxSize = maxSize; |
|||
_buffer = ArrayPool<byte>.Shared.Rent(maxSize); |
|||
_position = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 尝试将数据写入缓冲区
|
|||
/// </summary>
|
|||
/// <param name="data">要写入的数据</param>
|
|||
/// <param name="offset">数据偏移量</param>
|
|||
/// <param name="count">要写入的字节数</param>
|
|||
/// <returns>是否写入成功</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool TryWrite(byte[] data, int offset, int count) |
|||
{ |
|||
if (_isDisposed) |
|||
{ |
|||
throw new ObjectDisposedException(nameof(WebSocketMessageBuffer)); |
|||
} |
|||
|
|||
if (data == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(data)); |
|||
} |
|||
|
|||
if (offset < 0 || count < 0 || offset + count > data.Length) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(offset)); |
|||
} |
|||
|
|||
if (_position + count > _maxSize) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
System.Buffer.BlockCopy(data, offset, _buffer, _position, count); |
|||
_position += count; |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取当前缓冲区中的消息
|
|||
/// </summary>
|
|||
/// <returns>消息字节数组</returns>
|
|||
public byte[] GetMessage() |
|||
{ |
|||
if (_isDisposed) |
|||
{ |
|||
throw new ObjectDisposedException(nameof(WebSocketMessageBuffer)); |
|||
} |
|||
|
|||
if (_position == 0) |
|||
{ |
|||
return Array.Empty<byte>(); |
|||
} |
|||
|
|||
var result = new byte[_position]; |
|||
System.Buffer.BlockCopy(_buffer, 0, result, 0, _position); |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取当前缓冲区中的消息,使用内存池
|
|||
/// </summary>
|
|||
/// <returns>消息字节数组的租借对象</returns>
|
|||
public IMemoryOwner<byte> GetMessageWithMemoryPool() |
|||
{ |
|||
if (_isDisposed) |
|||
{ |
|||
throw new ObjectDisposedException(nameof(WebSocketMessageBuffer)); |
|||
} |
|||
|
|||
if (_position == 0) |
|||
{ |
|||
return new EmptyMemoryOwner(); |
|||
} |
|||
|
|||
var memoryOwner = MemoryPool<byte>.Shared.Rent(_position); |
|||
var span = memoryOwner.Memory.Span; |
|||
for (int i = 0; i < _position; i++) |
|||
{ |
|||
span[i] = _buffer[i]; |
|||
} |
|||
return memoryOwner; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 重置缓冲区
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Reset() |
|||
{ |
|||
if (_isDisposed) |
|||
{ |
|||
throw new ObjectDisposedException(nameof(WebSocketMessageBuffer)); |
|||
} |
|||
|
|||
_position = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 释放资源
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
if (!_isDisposed) |
|||
{ |
|||
ArrayPool<byte>.Shared.Return(_buffer); |
|||
_isDisposed = true; |
|||
} |
|||
} |
|||
|
|||
private sealed class EmptyMemoryOwner : IMemoryOwner<byte> |
|||
{ |
|||
public Memory<byte> Memory => Memory<byte>.Empty; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
// 空实现,因为 Empty 不需要释放
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CellularManagement.WebSocket.Handler; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket 错误处理器
|
|||
/// 提供错误处理和重试机制
|
|||
/// </summary>
|
|||
public class WebSocketErrorHandler |
|||
{ |
|||
private readonly ILogger _logger; |
|||
private readonly int _maxRetries; |
|||
private readonly TimeSpan _retryDelay; |
|||
|
|||
public WebSocketErrorHandler(ILogger logger, int maxRetries = 3, TimeSpan? retryDelay = null) |
|||
{ |
|||
_logger = logger; |
|||
_maxRetries = maxRetries; |
|||
_retryDelay = retryDelay ?? TimeSpan.FromSeconds(1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 使用重试机制执行操作
|
|||
/// </summary>
|
|||
/// <param name="action">要执行的操作</param>
|
|||
/// <returns>操作是否成功</returns>
|
|||
public async Task<bool> HandleWithRetryAsync(Func<Task> action) |
|||
{ |
|||
for (int i = 0; i < _maxRetries; i++) |
|||
{ |
|||
try |
|||
{ |
|||
await action(); |
|||
return true; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogWarning(ex, "操作失败,重试 {RetryCount}/{MaxRetries}", i + 1, _maxRetries); |
|||
if (i < _maxRetries - 1) |
|||
{ |
|||
await Task.Delay(_retryDelay); |
|||
} |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
} |
@ -0,0 +1,104 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Threading; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CellularManagement.WebSocket.Monitor; |
|||
|
|||
/// <summary>
|
|||
/// WebSocket 性能监控器
|
|||
/// 用于收集和记录 WebSocket 连接的性能指标
|
|||
/// </summary>
|
|||
public class WebSocketPerformanceMonitor |
|||
{ |
|||
private readonly ILogger _logger; |
|||
private readonly ConcurrentDictionary<string, ConnectionMetrics> _metrics = new(); |
|||
|
|||
public class ConnectionMetrics |
|||
{ |
|||
private long _totalMessages; |
|||
private long _totalBytes; |
|||
private long _processingTime; |
|||
private int _errorCount; |
|||
|
|||
public long TotalMessages => _totalMessages; |
|||
public long TotalBytes => _totalBytes; |
|||
public long ProcessingTime => _processingTime; |
|||
public long LastMessageTime { get; set; } |
|||
public int ErrorCount => _errorCount; |
|||
|
|||
public void IncrementMessages() => Interlocked.Increment(ref _totalMessages); |
|||
public void AddBytes(long bytes) => Interlocked.Add(ref _totalBytes, bytes); |
|||
public void AddProcessingTime(long time) => Interlocked.Add(ref _processingTime, time); |
|||
public void IncrementErrors() => Interlocked.Increment(ref _errorCount); |
|||
} |
|||
|
|||
public WebSocketPerformanceMonitor(ILogger logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 记录消息处理性能指标
|
|||
/// </summary>
|
|||
/// <param name="connectionId">连接ID</param>
|
|||
/// <param name="messageSize">消息大小</param>
|
|||
/// <param name="processingTime">处理时间(毫秒)</param>
|
|||
public void RecordMessage(string connectionId, int messageSize, long processingTime) |
|||
{ |
|||
var metrics = _metrics.GetOrAdd(connectionId, _ => new ConnectionMetrics()); |
|||
metrics.IncrementMessages(); |
|||
metrics.AddBytes(messageSize); |
|||
metrics.AddProcessingTime(processingTime); |
|||
metrics.LastMessageTime = DateTime.UtcNow.Ticks; |
|||
|
|||
if (metrics.TotalMessages % 100 == 0) |
|||
{ |
|||
LogMetrics(connectionId, metrics); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 记录错误
|
|||
/// </summary>
|
|||
/// <param name="connectionId">连接ID</param>
|
|||
public void RecordError(string connectionId) |
|||
{ |
|||
if (_metrics.TryGetValue(connectionId, out var metrics)) |
|||
{ |
|||
metrics.IncrementErrors(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取连接的性能指标
|
|||
/// </summary>
|
|||
/// <param name="connectionId">连接ID</param>
|
|||
/// <returns>性能指标</returns>
|
|||
public ConnectionMetrics GetMetrics(string connectionId) |
|||
{ |
|||
return _metrics.TryGetValue(connectionId, out var metrics) ? metrics : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 清理连接的性能指标
|
|||
/// </summary>
|
|||
/// <param name="connectionId">连接ID</param>
|
|||
public void ClearMetrics(string connectionId) |
|||
{ |
|||
_metrics.TryRemove(connectionId, out _); |
|||
} |
|||
|
|||
private void LogMetrics(string connectionId, ConnectionMetrics metrics) |
|||
{ |
|||
_logger.LogInformation( |
|||
"连接性能指标 - ID: {ConnectionId}, 消息数: {MessageCount}, " + |
|||
"总字节数: {TotalBytes}, 平均处理时间: {AvgProcessingTime}ms, " + |
|||
"错误数: {ErrorCount}", |
|||
connectionId, |
|||
metrics.TotalMessages, |
|||
metrics.TotalBytes, |
|||
metrics.ProcessingTime / metrics.TotalMessages, |
|||
metrics.ErrorCount); |
|||
} |
|||
} |
Loading…
Reference in new issue