奇怪的数据包从Chrome websocket客户端到达C#websocket服务器

问题描述 投票:1回答:1

我有一个C#服务器端websocket代码,我从Chrome(javascript websocket客户端)发送测试数据。

接下来:https://stackoverflow.com/a/8125509/2508439和其他一些链接,我创建了我的C#webserver(服务器端)解码函数,如下所示:

public String DecodeMessage(Byte[] bytes)
{
    Console.WriteLine("+DecodeMessage+");
    String incomingData = String.Empty;
    Byte secondByte = bytes[1];
    bool masked = (bytes[1] & 128) != 0;
    //long dataLength = secondByte & 127;
    dataLength = secondByte & 127;
    //Int32 indexFirstMask = 2;
    indexFirstMask = 2;
    if (masked)
    {
        Console.WriteLine("Masked bit SET");
    }
    if (dataLength == 126)
    {
        indexFirstMask = 4;
        dataLength = bytes[3] | bytes[2] << 8;
    }
    else if (dataLength == 127)
    {
        indexFirstMask = 10;
        dataLength = bytes[9] | bytes[8] << 8 | bytes[7] << 16 | bytes[6] << 24 | bytes[5] << 32 |
            bytes[4] << 40 | bytes[3] << 48 | bytes[2] << 56;
    }

    //IEnumerable<Byte> keys = bytes.Skip(indexFirstMask).Take(4);
    keys = bytes.Skip(indexFirstMask).Take(4);
    Int32 indexFirstDataByte = indexFirstMask + 4;

    Byte[] decoded = new Byte[bytes.Length - indexFirstDataByte];
    Console.WriteLine("dataLength : " + dataLength + " ; bytes.Length : " + bytes.Length);
    Int32 j = 0;
    for (Int32 i = indexFirstDataByte; i < bytes.Length; i++)
    {
        decoded[j] = (Byte)(bytes[i] ^ keys.ElementAt(j % 4));
        j++;
    }
    Console.WriteLine("-DecodeMessage-");

    return incomingData = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
}
public String DecodeRemainingMessage(Byte[] bytes, long bytesAlreadyRead)
{
    Console.WriteLine("+DecodeRemainingMessage+");
    String incomingData = String.Empty;

    Int32 indexFirstDataByte = 0;
    if ( indexFirstMask == 10 )//special case, what to do here?
    {
        indexFirstDataByte = 10;
    }

    Byte[] decoded = new Byte[bytes.Length - indexFirstDataByte];
    //Byte[] decoded = new Byte[bytes.Length];
    Int32 j = 0;
    for (Int32 i = indexFirstDataByte; i < bytes.Length; i++)
    {
        decoded[j] = (Byte)(bytes[i] ^ keys.ElementAt(j % 4));
        j++;
    }
    Console.WriteLine("-DecodeRemainingMessage-");

    return incomingData = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
}

简单的数据包(125或更小的到达就好了)。

如果大小约125并且小于65535,也可以很好地到达(种类:有一些细节,但我现在没有进入[*])。

65535以上的数据包:整个解码功能变得疯狂:

只有第一次正确解码数据包,之后,我收到的数据完全是二进制(不可读),并且在第一个数据包到达后,连续数据包中:

if (dataLength == 126)
{
    ...
}
else if (dataLength == 127) ...

两个条件永远不会满足,并且dataLength总是小于126,然后将其解码为(小)数据包,因此永远不会正确地重建。

谁能突出我可能做错了什么?

谢谢

[*] =>低于65535长度的数据有时会出现两个以上的数据包,然后其行为与较大的数据包相同,并且第一次触发此功能后的数据包永远不会再次正确重建。

编辑1:@Marc

根据你的评论,我在上面的函数中放了'masked bit check',我可以看到它总是被设置为'1'(正如预期的那样,因为现在这只是服务器端代码)。

我也在不同的函数中解析控制框架,在这个函数中,如果我的代码是正确的,我可能会得到大量的垃圾数据。

详细说明,请参阅以下这些功能:

整个逻辑代码:

枚举:

public enum ControlFrame { NA=0, CloseConnection=1, Ping=2, Pong=4, Text=8, Binary=16, ContinueFrame =32, FinalFrame=64 };

解析控制框架功能:

private int ParseControlFrame(byte controlFrame)
{
    int rv = (int)ControlFrame.NA;
    bool isFinalFrame = (controlFrame & 0x80) == 0x80 ;
    byte opCode = (byte)((controlFrame & 0x0F));
    if ( opCode >= 0x3 && opCode <= 0x7    ||
        opCode >= 0xB && opCode <= 0xF    )//special frame, ignore it
    {
        Console.WriteLine("Reserved Frame received");
        return rv;
    }
    if (opCode == 0x8 || opCode == 0x0 || opCode == 0x1 || opCode == 0x2 || opCode == 0x9 || opCode == 0xA) //proceed furter
    {
        if (opCode == 0x0) //continue frame
        {
            rv |= (int)ControlFrame.ContinueFrame;
            Console.WriteLine("Continue Frame received");
        }
        if (opCode == 0x1) //text frame
        {
            rv |= (int)ControlFrame.Text;
            Console.WriteLine("Text Frame received");
        }
        if (opCode == 0x2) //binary frame
        {
            rv |= (int)ControlFrame.Binary;
            Console.WriteLine("Binary frame received");
        }
        if (opCode == 0x8) //connection closed
        {
            rv |= (int)ControlFrame.CloseConnection;
            Console.WriteLine("CloseConnection Frame received");
        } 
        if (opCode == 0x9) //ping
        {
            rv |= (int)ControlFrame.Ping;
            Console.WriteLine("PING received");
        }
        if (opCode == 0xA) //pong
        {
            rv |= (int)ControlFrame.Pong;
            Console.WriteLine("PONG received");
        }
    }
    else // invalid control bit, must close the connection
    {
        Console.WriteLine("invalid control frame received, must close connection");
        rv = (int)ControlFrame.CloseConnection;
    }
    if (isFinalFrame) //Final frame ...
    {
        rv |= (int)ControlFrame.FinalFrame;
        Console.WriteLine("Final frame received");
    }
    //else
    //{
    //    rv |= (int)ControlFrame.ContinueFrame;
    //    Console.WriteLine("Continue frame received");
    //}
    return rv;
}

逻辑流程(实际的代码片段):

if (stream.DataAvailable)
{
    long bytesAlreadyRead = 0;
    bool breakMain = false;
    while (client.Available > 0 )
    {
        byte[] bytes = new byte[client.Available];

        stream.Read(bytes, 0, bytes.Length);

        Console.WriteLine("if (stream.DataAvailable):\nclient.Available : " + client.Available +
            " ; bytes.Length : " + bytes.Length);
        //translate bytes of request to string
        String data = Encoding.UTF8.GetString(bytes);
        Console.WriteLine("Message received on: " + DateTime.Now);

        if (bytesAlreadyRead == 0)
        {
            int controlFrame = ParseControlFrame(bytes[0]);
            if (controlFrame == (int)ControlFrame.NA ||
                (int)(controlFrame & (int)ControlFrame.Ping) > 0 ||
                (int)(controlFrame & (int)ControlFrame.Pong) > 0)   //ignore it
            {
            }
            else
            {
                if ((int)(controlFrame & (int)ControlFrame.CloseConnection) > 0)
                {
                    Console.WriteLine("Connection #" + c.Key + " Closed on: " + DateTime.Now);
                    tcpClients.Remove(c.Key);
                    breakMain = true;
                    break;
                }
                else
                {
                    string result = c.Value.DecodeMessage(bytes);
                    File.WriteAllText("recvfile.txt", result);
                }
            }
        }
        else
        {
            string result = c.Value.DecodeRemainingMessage(bytes, bytesAlreadyRead);
            File.AppendAllText("recvfile.txt", "\n");
            File.AppendAllText("recvfile.txt", result);
        }
        bytesAlreadyRead += bytes.Length;
    }
    if ( breakMain == true )
    {
        break;
    }
}

我没有垃圾,但数据丢失了。

如果我不进行此检查,那么,我开始接收垃圾。

基于Console.WriteLine输出,我看到类似于小于65535的数据:

Message received on: 12/29/2017 12:59:00 PM
Text Frame received
Final frame received
Masked bit SET
Message received on: 12/29/2017 12:59:12 PM
Text Frame received
Final frame received
Masked bit SET

对于65535以上的数据:

Message received on: 12/29/2017 1:02:51 PM
Text Frame received
Final frame received
Masked bit SET
Message received on: 12/29/2017 1:02:51 PM
Reserved Frame received

即小于65535,我很好(大部分时间)。

超过65535,事情变得奇怪。

当你提到:

I wonder if what is happening is that you're getting multiple messages in a single Read call (perfectly normal in TCP), consuming the first message, and incorrectly treating the entire bytes as consumed.

我以前从未想过这个,也许我还需要以某种方式处理这个问题?

编辑2:

根据你的评论,我修改了'if(stream.DataAvailable)'逻辑,所以它继续在while循环中读取数据,直到实际刷新了本地存储的所有数据。

所以我可能接近解决它(感谢您的反馈),但第二次调用DecodeMessage()函数,它仍然在垃圾(二进制)数据中解密。

我正在努力搞清楚!

谢谢

编辑3:

好的,根据你的建议,我已经整理了大部分逻辑。但是,'DecodeRemainingMessage'功能中的特殊情况仍然是个谜。 [我将一些变量从局部范围转移到了类范围,因此它们在函数中被注释掉] ......

如果我弄错了,我不需要在这里放置任何特殊条件,但在这种情况下,我仍然会收到垃圾。

有什么指针吗?

[对不起凌乱的代码,一旦我得到正确的图片就会更新!]

谢谢

编辑4:在评论/聊天中的所有建议后帮助我达到了大大更新解码逻辑的程度,并且在高于65535字节的情况下仍然无法获得正确的数据。但是当我尝试使用Firefox的最终逻辑时,我得到了所有数据!非常感谢你,而且,我仍然需要弄清楚如何处理有问题的Chrome客户端!谢谢!!

c# google-chrome websocket
1个回答
1
投票

编辑:您的代码假定传入的帧始终被屏蔽。如果您的代码只是服务器,那么这可能是正常的,但您可能想要检查bytes[1] & 128是设置(屏蔽)还是清除(未屏蔽)。如果未屏蔽:标头缩短了4个字节。如果这只是一个服务器,那么你应该没问题(从RFC6455中的5.2开始):

从客户端发送到服务器的所有帧都将此位设置为1。

但是:仔细检查会很好。你会惊讶地发现有多少违规的客户违反规范。

--

总体代码看起来很好,并且与what I have here大致相当。我什么都看不出来。这让我怀疑这里的问题是TCP流媒体;很明显,你的方法正在做任何事情来报告该帧逻辑上应该消耗多少字节数 - 即总报头长度加上有效载荷长度。为了与我的代码进行比较,这将是out int headerLengthframe.PayloadLength的组合。

我想知道发生的事情是你在一个Read调用中获得多个消息(在TCP中完全正常),消耗第一条消息,并错误地将整个bytes视为已消耗。这意味着您开始在下一帧标题的错误位置读取。单个Read调用可以返回一帧,一帧或多于一帧的片段 - 它唯一不能返回的是0字节,除非套接字已关闭。

© www.soinside.com 2019 - 2024. All rights reserved.