You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在.NET Framework中如何用闭包实现System.Net.WebSockets.ClientWebSocket事件化消息处理?

你的理解完全正确!

没错,System.Net.WebSockets.ClientWebSocket确实没有提供你在macOS/iOS开发中习惯的事件驱动式API,它采用的是需要主动、持续调用ReadAsync方法来拉取消息的传统模式——这是它作为底层WebSocket客户端实现的设计特点,目的是给开发者最大的灵活性来控制线程、循环逻辑和资源管理。

要把它改造成你熟悉的“消息到达时自动触发代码块”的形式,我们可以自己封装一层,把主动拉取的逻辑包装成事件驱动的模型,同时适配你习惯的函数式编程风格。下面是具体的实现思路和代码示例:

1. 封装一个事件驱动的WebSocket客户端类

我们可以创建一个自定义类,内部管理ClientWebSocket的生命周期,在后台线程中运行消息读取循环,当收到消息时触发自定义事件(或者直接调用你传入的回调函数)。

using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

public class EventDrivenWebSocket : IDisposable
{
    private ClientWebSocket _webSocket;
    private CancellationTokenSource _cts;
    private bool _isDisposed;

    // 定义消息接收事件,符合.NET事件规范
    public event EventHandler<string> MessageReceived;
    // 定义连接断开事件
    public event EventHandler Disconnected;

    // 也可以提供函数式的回调注册方式,贴近你习惯的闭包用法
    public void OnMessageReceived(Action<string> callback)
    {
        MessageReceived += (sender, message) => callback(message);
    }

    public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken = default)
    {
        _webSocket = new ClientWebSocket();
        _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        
        await _webSocket.ConnectAsync(uri, _cts.Token);
        
        // 启动后台消息监听循环
        _ = Task.Run(ListenForMessagesAsync, _cts.Token);
    }

    public async Task SendMessageAsync(string message, CancellationToken cancellationToken = default)
    {
        if (_webSocket.State != WebSocketState.Open)
            throw new InvalidOperationException("WebSocket is not open.");

        var buffer = Encoding.UTF8.GetBytes(message);
        var segment = new ArraySegment<byte>(buffer);
        await _webSocket.SendAsync(segment, WebSocketMessageType.Text, true, cancellationToken);
    }

    private async Task ListenForMessagesAsync()
    {
        var buffer = new byte[1024 * 4];
        try
        {
            while (_webSocket.State == WebSocketState.Open && !_cts.IsCancellationRequested)
            {
                var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token);
                
                if (result.MessageType == WebSocketMessageType.Close)
                {
                    await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                    break;
                }

                var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
                // 触发消息接收事件
                MessageReceived?.Invoke(this, message);
            }
        }
        catch (OperationCanceledException)
        {
            // 预期的取消操作,无需处理
        }
        catch (Exception ex)
        {
            // 这里可以添加异常处理逻辑,比如触发错误事件
            Console.WriteLine($"WebSocket error: {ex.Message}");
        }
        finally
        {
            // 触发断开连接事件
            Disconnected?.Invoke(this, EventArgs.Empty);
            Dispose();
        }
    }

    public void Dispose()
    {
        if (_isDisposed) return;
        
        _cts?.Cancel();
        _webSocket?.Dispose();
        _cts?.Dispose();
        
        _isDisposed = true;
    }
}

2. 使用封装后的客户端(两种方式)

方式1:使用.NET事件模式

var wsClient = new EventDrivenWebSocket();

// 订阅消息接收事件
wsClient.MessageReceived += (sender, message) =>
{
    // 这里就是你要处理消息的代码块,相当于闭包
    Console.WriteLine($"Received message: {message}");
    // 可以在这里把消息转发到其他业务逻辑模块
};

// 订阅断开事件
wsClient.Disconnected += (sender, args) =>
{
    Console.WriteLine("WebSocket disconnected.");
};

// 连接到服务器
await wsClient.ConnectAsync(new Uri("wss://your-private-push-server.com"));

// 发送测试消息
await wsClient.SendMessageAsync("Hello from event-driven client!");

// 保持程序运行(实际项目中根据你的应用类型调整,比如WPF/WinForms的消息循环)
Console.ReadLine();

// 清理资源
wsClient.Dispose();

方式2:使用函数式回调(更贴近你习惯的闭包风格)

var wsClient = new EventDrivenWebSocket();

// 直接传入回调函数(闭包)
wsClient.OnMessageReceived(message =>
{
    Console.WriteLine($"Received via callback: {message}");
    // 在这里处理消息,比如更新UI、触发业务逻辑等
});

await wsClient.ConnectAsync(new Uri("wss://your-private-push-server.com"));
// ... 后续逻辑同上

关键注意事项

  • 线程安全:如果你的回调/事件处理逻辑涉及UI操作(比如WinForms/WPF),需要切换到UI线程执行(比如使用Dispatcher.InvokeControl.Invoke),因为消息监听循环运行在后台线程。
  • 异常处理:封装类中已经做了基础的异常捕获,你可以根据需求扩展错误事件,或者在回调中处理特定业务异常。
  • 资源管理:一定要调用Dispose方法清理WebSocket和取消令牌资源,避免内存泄漏。

这种封装方式既保留了ClientWebSocket的底层灵活性,又给你提供了熟悉的事件/函数式编程体验,完美适配你的私有推送通知服务需求。

内容的提问来源于stack exchange,提问作者ygini

火山引擎 最新活动