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