C# 在两个流之间代理WebSocket消息

C# 在两个流之间代理WebSocket消息,c#,websocket,tcpclient,C#,Websocket,Tcpclient,我有一个充当中间人的HTTP代理服务器。其基本功能如下: 侦听客户端浏览器请求 将请求转发到服务器 解析服务器响应 将响应转发回客户端浏览器 因此,基本上在客户端浏览器和代理之间有一个网络流,或者更常见的是一个SslStream,在代理和服务器之间有另一个 还需要在客户端和服务器之间转发WebSocket流量 因此,现在当客户端浏览器请求升级到websocket的连接时,远程服务器用HTTP代码101进行响应,代理服务器会维护这些连接,以便将进一步的消息从客户端转发到服务器,反之亦然 因此,

我有一个充当中间人的HTTP代理服务器。其基本功能如下:

  • 侦听客户端浏览器请求
  • 将请求转发到服务器
  • 解析服务器响应
  • 将响应转发回客户端浏览器
因此,基本上在客户端浏览器和代理之间有一个
网络流
,或者更常见的是一个
SslStream
,在代理和服务器之间有另一个

还需要在客户端和服务器之间转发WebSocket流量

因此,现在当客户端浏览器请求升级到websocket的连接时,远程服务器用HTTP代码101进行响应,代理服务器会维护这些连接,以便将进一步的消息从客户端转发到服务器,反之亦然

因此,在代理收到来自远程服务器的消息,表示它已准备好切换协议之后,它需要进入一个循环,在该循环中,客户端和服务器流都会被轮询以获取数据,并且任何接收到的数据都会被转发给另一方

问题

WebSocket允许双方随时发送消息。这对于诸如ping/pong之类的控制消息来说尤其是一个问题,其中任何一方都可以在任何时候发送ping,而另一方则需要及时用pong进行回复。现在考虑两个例子:<代码> SSLFiels,它没有<代码>数据可用> <代码>属性,其中读取数据的唯一方法是调用<代码>读取<代码> />代码> ReaSycyc<代码>,在某些数据可用之前,它可能不会返回。考虑下面的伪代码:

public async Task GetMessage()
{
    // All these methods that we await read from the source stream
    byte[] firstByte = await GetFirstByte(); // 1-byte buffer
    byte[] messageLengthBytes = await GetMessageLengthBytes();
    uint messageLength = GetMessageLength(messageLengthBytes);
    bool isMessageMasked = DetermineIfMessageMasked(messageLengthBytes);
    byte[] maskBytes;
    if (isMessageMasked)
    {
        maskBytes = await GetMaskBytes();
    }

    byte[] messagePayload = await GetMessagePayload(messageLength);

    // This method writes to the destination stream
    await ComposeAndForwardMessageToOtherParty(firstByte, messageLengthBytes, maskBytes, messagePayload);
}

上面的伪代码从一个流读取,然后写入另一个流。问题是上述过程需要同时为两个流运行,因为我们不知道哪一方会在任何给定的时间点向另一方发送消息。然而,当读操作处于活动状态时,不可能执行写操作。因为我们没有必要的方法来轮询传入数据,所以读取操作必须是阻塞的。这意味着如果我们同时开始两个流的读取操作,我们可能会忘记写入它们。一个流最终将返回一些数据,但我们无法将该数据发送到另一个流,因为它仍将忙于读取。这可能需要一段时间,至少在拥有该流的一方发送ping请求之前。

感谢@MarcGravel的评论,我们了解到网络流支持独立的读/写操作,也就是说,
NetworkStream
充当两个独立的管道——一个读,一个写——它是全双工的

因此,代理WebSocket消息就像启动两个独立的任务一样简单,一个从客户端流读取并写入服务器流,另一个从服务器流读取并写入客户端流

如果它对搜索它的任何人都有帮助,下面是我如何实现它的:

public class WebSocketRequestHandler
{
    private const int MaxMessageLength = 0x7FFFFFFF;

    private const byte LengthBitMask = 0x7F;

    private const byte MaskBitMask = 0x80;

    private delegate Task WriteStreamAsyncDelegate(byte[] buffer, int offset, int count, CancellationToken cancellationToken);

    private delegate Task<byte[]> BufferStreamAsyncDelegate(int count, CancellationToken cancellationToken);

    public async Task HandleWebSocketMessagesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        var clientListener = ListenForClientMessages(cancellationToken);
        var serverListener = ListenForServerMessages(cancellationToken);
        await Task.WhenAll(clientListener, serverListener);
    }

    private async Task ListenForClientMessages(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            cancellationToken.ThrowIfCancellationRequested();
            await ListenForMessages(YOUR_CLIENT_STREAM_BUFFER_METHOD_DELEGATE, YOUR_SERVER_STREAM_WRITE_METHOD_DELEGATE, cancellationToken);
        }
    }

    private async Task ListenForServerMessages(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            cancellationToken.ThrowIfCancellationRequested();
            await ListenForMessages(YOUR_SERVER_STREAM_BUFFER_METHOD_DELEGATE, YOUR_CLIENT_STREAM_WRITE_METHOD_DELEGATE, cancellationToken);
        }
    }

    private static async Task ListenForMessages(BufferStreamAsyncDelegate sourceStreamReader,
        WriteStreamAsyncDelegate destinationStreamWriter,
        CancellationToken cancellationToken)
    {
        var messageBuilder = new List<byte>();
        var firstByte = await sourceStreamReader(1, cancellationToken);
        messageBuilder.AddRange(firstByte);
        var lengthBytes = await GetLengthBytes(sourceStreamReader, cancellationToken);
        messageBuilder.AddRange(lengthBytes);
        var isMaskBitSet = (lengthBytes[0] & MaskBitMask) != 0;
        var length = GetMessageLength(lengthBytes);
        if (isMaskBitSet)
        {
            var maskBytes = await sourceStreamReader(4, cancellationToken);
            messageBuilder.AddRange(maskBytes);
        }

        var messagePayloadBytes = await sourceStreamReader(length, cancellationToken);
        messageBuilder.AddRange(messagePayloadBytes);
        await destinationStreamWriter(messageBuilder.ToArray(), 0, messageBuilder.Count, cancellationToken);
    }

    private static async Task<byte[]> GetLengthBytes(BufferStreamAsyncDelegate sourceStreamReader, CancellationToken cancellationToken)
    {
        var lengthBytes = new List<byte>();
        var firstLengthByte = await sourceStreamReader(1, cancellationToken);
        lengthBytes.AddRange(firstLengthByte);
        var lengthByteValue = firstLengthByte[0] & LengthBitMask;
        if (lengthByteValue <= 125)
        {
            return lengthBytes.ToArray();
        }

        switch (lengthByteValue)
        {
            case 126:
            {
                var secondLengthBytes = await sourceStreamReader(2, cancellationToken);
                lengthBytes.AddRange(secondLengthBytes);
                return lengthBytes.ToArray();
            }
            case 127:
            {
                var secondLengthBytes = await sourceStreamReader(8, cancellationToken);
                lengthBytes.AddRange(secondLengthBytes);
                return lengthBytes.ToArray();
            }
            default:
                throw new Exception($"Unexpected first length byte value: {lengthByteValue}");
        }
    }

    private static int GetMessageLength(byte[] lengthBytes)
    {
        byte[] subArray;
        switch (lengthBytes.Length)
        {
            case 1:
                return lengthBytes[0] & LengthBitMask;

            case 3:
                if (!BitConverter.IsLittleEndian)
                {
                    return BitConverter.ToUInt16(lengthBytes, 1);
                }

                subArray = lengthBytes.SubArray(1, 2);
                Array.Reverse(subArray);
                return BitConverter.ToUInt16(subArray, 0);

            case 9:
                subArray = lengthBytes.SubArray(1, 8);
                Array.Reverse(subArray);
                var retVal = BitConverter.ToUInt64(subArray, 0);
                if (retVal > MaxMessageLength)
                {
                    throw new Exception($"Unexpected payload length: {retVal}");
                }

                return (int) retVal;

            default:
                throw new Exception($"Impossibru!!1 The length of lengthBytes array was: '{lengthBytes.Length}'");
        }
    }
}
公共类WebSocketRequestHandler
{
private const int MaxMessageLength=0x7FFFFFFF;
私有常量字节长度位掩码=0x7F;
私有常量字节MaskBitMask=0x80;
私有委托任务WriteStreamAsyncDelegate(字节[]缓冲区、int偏移量、int计数、CancellationToken CancellationToken);
私有委托任务BufferStreamAsyncDelegate(int计数、CancellationToken CancellationToken);
公共异步任务HandleWebSocketMessagesAsync(CancellationToken CancellationToken=default(CancellationToken))
{
var clientListener=ListenForClientMessages(cancellationToken);
var serverListener=ListenForServerMessages(cancellationToken);
wait Task.WhenAll(clientListener、serverListener);
}
专用异步任务ListenForClientMessages(CancellationToken CancellationToken)
{
而(!cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
等待ListenForMessages(您的\u客户端\u流\u缓冲区\u方法\u委托、您的\u服务器\u流\u写入\u方法\u委托、取消令牌);
}
}
专用异步任务ListenForServerMessages(CancellationToken CancellationToken)
{
而(!cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
等待ListenForMessages(您的\u服务器\u流\u缓冲区\u方法\u委托、您的\u客户端\u流\u写入\u方法\u委托、取消令牌);
}
}
专用静态异步任务ListenFormMessages(BufferStreamAsyncDelegate sourceStreamReader,
WriteStreamAsyncDelegate destinationStreamWriter,
取消令牌(取消令牌)
{
var messageBuilder=新列表();
var firstByte=等待sourceStreamReader(1,cancellationToken);
messageBuilder.AddRange(第一字节);
var lengthBytes=等待GetLengthBytes(sourceStreamReader,cancellationToken);
messageBuilder.AddRange(长度字节);
var isMaskBitSet=(lengthBytes[0]&MaskBitMask)!=0;
var length=GetMessageLength(lengthBytes);
if(isMaskBitSet)
{
var maskBytes=等待sourceStreamReader(4,cancellationToken);
messageBuilder.AddRange(maskBytes);
}
var messagePayloadBytes=wait sourceStreamReader(长度、取消令牌);
messageBuilder.AddRange(messagePayloadBytes);
等待destinationStreamWriter(messageBuilder.ToArray(),0,messageBuilder.Count,cancellationToken);
}
专用静态异步任务GetLengthBytes(BufferStreamAsyncDelegate sourceStreamReader,CancellationToken CancellationToken)
{
var lengthBytes=新列表();
var firstLengthByte=等待sourceStreamReader(1,cancellationToken);
lengthBytes.AddRange(firstLengthByte);
var lengthByteValue=firstLengthByte[0]&长度位掩码;
如果(长度比)