整理代码
大石头 authored at 2025-07-14 01:02:05
3.94 KiB
NewLife.Remoting
using System.Net;
using System.Net.WebSockets;
using NewLife.Remoting.Models;
using NewLife.Remoting.Services;
using NewLife.Security;
using NewLife.Serialization;

namespace NewLife.Remoting.Extensions.Services;

/// <summary>WebSocket设备会话</summary>
public class WsCommandSession(WebSocket socket) : CommandSession
{
    /// <summary>是否活动中</summary>
    public override Boolean Active => socket != null && socket.State == WebSocketState.Open;

    /// <summary>销毁</summary>
    protected override void Dispose(Boolean disposing)
    {
        base.Dispose(disposing);

        try
        {
            if (socket != null && socket.State == WebSocketState.Open)
                socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Dispose", default);
        }
        catch { }
        finally
        {
            socket?.Dispose();
        }
    }

    /// <summary>处理事件消息,通过WebSocket向下发送</summary>
    /// <param name="command"></param>
    /// <param name="message"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public override Task HandleAsync(CommandModel command, String message, CancellationToken cancellationToken)
    {
        message ??= command.ToJson();
        return socket.SendAsync(message.GetBytes(), WebSocketMessageType.Text, true, cancellationToken);
    }

    /// <summary>阻塞WebSocket,等待连接结束</summary>  
    /// <param name="context"></param>  
    /// <param name="cancellationToken"></param>  
    /// <returns></returns>  
    public virtual async Task WaitAsync(HttpContext context, CancellationToken cancellationToken)
    {
        var ip = context.GetUserHost();
        var sid = Rand.Next();
        var connection = context.Connection;
        var address = connection.RemoteIpAddress ?? IPAddress.Loopback;
        if (address.IsIPv4MappedToIPv6) address = address.MapToIPv4();
        var remote = new IPEndPoint(address, connection.RemotePort);
        Log?.WriteLog("WebSocket连接", true, $"State={socket.State} sid={sid} Remote={remote}");

        // 长连接上线  
        SetOnline?.Invoke(true);

        // 链接取消令牌。当客户端断开时,触发取消,结束长连接  
        using var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        try
        {
            var buf = new Byte[64];
            while (!source.IsCancellationRequested && socket.State == WebSocketState.Open)
            {
                var data = await socket.ReceiveAsync(new ArraySegment<Byte>(buf), source.Token).ConfigureAwait(false);
                if (data.MessageType == WebSocketMessageType.Close) break;
                if (data.MessageType is WebSocketMessageType.Text or WebSocketMessageType.Binary)
                {
                    var txt = buf.ToStr(null, 0, data.Count);
                    if (txt == "Ping")
                    {
                        await socket.SendAsync("Pong".GetBytes(), WebSocketMessageType.Text, true, source.Token).ConfigureAwait(false);

                        // 长连接上线。可能客户端心跳已经停了,WS还在,这里重新上线  
                        SetOnline?.Invoke(true);
                    }
                }
            }

            if (!source.IsCancellationRequested) source.Cancel();

            if (socket.State == WebSocketState.Open)
                await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "finish", default).ConfigureAwait(false);
        }
        catch (TaskCanceledException) { }
        catch (OperationCanceledException) { }
        catch (WebSocketException ex)
        {
            Log?.WriteLog("WebSocket异常", false, ex.Message);
        }
        finally
        {
            source.Cancel();
            Log?.WriteLog("WebSocket断开", true, $"State={socket.State} CloseStatus={socket.CloseStatus} sid={sid} Remote={remote}");

            // 长连接下线  
            SetOnline?.Invoke(false);
        }
    }
}