0

我用 Javascript 编写了一个 WebSocket 客户端,它与我的服务器的 C# 连接并握手。目前,在“刷新”我的输出流时(仅在我将标头 BACK 发送给客户端并确认我的连接之后),客户端崩溃并在我下次尝试写入时生成服务器端异常。后一种行为是预期的,但是我无法弄清楚为什么刷新会断开连接。我在服务器端使用 TcpListener 和带有 StreamWriter 的 Socket,在客户端使用普通 WebSocket。

这种情况真正令人困惑的是,在“握手”的传输过程中,文本可以双向传输,并且在每行发送后执行 Flush,但是一旦握手完成,flush 就会终止连接。

请告诉我此修订中是否有足够的信息;因为它现在已经修改了几次。

提前致谢。

客户端Javascript:

<!DOCTYPE html>  
<meta charset="utf-8" />
<html>
<head>
<script language="javascript" type="text/javascript">
    var wsUri = "ws://127.0.0.1:9002/cc";
    var output;
    var websocket = null;

    function init()
    {
        StartWebSocket();
    }

    function StartWebSocket()
    {
        output = document.getElementById("output");
        writeToScreen("#WebSocket Starting");
        websocket = new WebSocket(wsUri,"lorem.ipsum.com");
        writeToScreen("#WebSocket Instantiated");
        websocket.removeEventListener("open",onOpen,false);
        websocket.addEventListener("open",onOpen,false);

        websocket.removeEventListener("close",onClose,false);
        websocket.addEventListener("close",onClose,false);

        websocket.removeEventListener("message",onMessage,false);
        websocket.addEventListener("message",onMessage,false);

        websocket.removeEventListener("error",onError,false);
        websocket.addEventListener("error",onError,false);

        writeToScreen("#WebSocket Events Attached");
    }

    function onOpen(evt)
    {
        try
        {
            writeToScreen("#WebSocket Connection Established");
            writeToScreen("#WebSocket BinaryType: " + websocket.binaryType);
            writeToScreen("#WebSocket Protocol: " + websocket.protocol);
            writeToScreen("#WebSocket Extensions: " + websocket.extensions);
            doSend("TestOutput\r\n\r");
        }
        catch( e )
        {
            writeToScreen(e);   
        }
    }

    function onClose(evt)
    {
        writeToScreen("#WebSocket Connection Aborted:");
        writeToScreen("&nbsp;&nbsp;&nbsp;&nbsp;Reason: " + evt.code );
        writeToScreen("&nbsp;&nbsp;&nbsp;&nbsp;Reason: " + evt.reason );
        writeToScreen("&nbsp;&nbsp;&nbsp;&nbsp;Clean: " + evt.wasClean);
    }

    function onMessage(evt)
    {
        writeToScreen("#WebSocket Message Event");
        try
        {
            writeToScreen("<span style=\"color: blue;\">#WebSocket Server Message: " + evt.data+"</span>");
        }
        catch( e )
        {
            writeToScreen(e);
        }
    }

    function onError(evt)
    {
        writeToScreen("<span style=\"color: red;\">#WebSocket Error:</span> " + evt.data);
    }

    function doSend(message)
    {
        try
        {
            websocket.send(message);
            writeToScreen("#WebSocket Output Written to Server: " + message);
        }
        catch( e ) 
        {
            writeToScreen(e);
        }
    }

    function writeToScreen(message)
    {
        try
        {
            var pre = document.createElement("a");
            pre.style.wordWrap = "break-word";
            pre.innerHTML = message + "<br>";
            output.appendChild(pre);
        }
        catch( e )
        {
            writeToScreen(e);
        }
    }

    window.addEventListener("load", init, false);

</script>
</head>
<body>
<div id="output"></div>
</body>
</html>

我从客户那里收到的握手报价如下:

GET /cc HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:9002
Origin: http://localhost
Sec-WebSocket-Key: icajBpkAfgA+YbVheBpDsQ==
Sec-WebSocket-Version: 13

我这样解释握手:

public override void Interpret(string Argument)
{
    if (String.IsNullOrEmpty(Argument))
    {
        return;
    }
    else
    {
        if( !HeaderFinished )
        {
            if (!HeaderStarted)
            {
                if (Argument.StartsWith("GET /"))
                {
                    this.Role = "client";
                    HeaderStarted = true;
                    this.Server.Print("Connection at " + this.Address + " set to client.");
                }
                else
                {
                    return;
                }
            }
            else
            {
                if (Argument.StartsWith("Sec-WebSocket-Key:"))
                {
                    this.Key = Argument.Split(' ')[1].TrimEnd().TrimStart();
                    return;
                }
                else if (Argument.StartsWith("Sec-WebSocket-Version:"))
                {
                    this.HeaderFinished = true;
                    this.WriteHeaderResponse();
                    HeaderSent = true;
                    return;
                }
            }
        }
        else
        {
            this.InterpretMessage(DecodeMessage(Argument));
            return;
        }
    }
}

发送我的标题响应:

public void WriteHeaderResponse()
{
    this.WriteLine("HTTP/1.1 101 Switching Protocols");
    this.WriteLine("Upgrade: websocket");
    this.WriteLine("Connection: Upgrade");
    String NewKey = ComputeResponseKey(this.Key);
    this.WriteLine("Sec-WebSocket-Accept: " + NewKey);
    this.WriteLine("Sec-WebSocket-Protocol: lorem.ipsum.com");
    this.WriteLine("\r\n");
}

并从客户端获取以下输出(此时):

#WebSocket Starting
#WebSocket Instantiated
#WebSocket Events Attached
#WebSocket Connection Established
#WebSocket BinaryType: blob
#WebSocket Protocol: lorem.ipsum.com
#WebSocket Extensions: 
#WebSocket Output Written to Server: TestOutput

此时,如果我尝试执行以下服务器端方法,客户端会像这样断开连接:

#WebSocket Connection Aborted:
    Reason: 1006
    Reason: 
    Clean: false

消息代码:-取自我在网上找到的东西,稍作修改......

public void WriteMessage(byte[] Payload)
{
    byte[] Message;
    int Length = Payload.Length;
    int MaskLength = 4;

    if (Length < 126)
    {
        Message = new byte[2 + MaskLength + Length];
        Message[1] = (byte)Length;
    }
    else if (Length < 65536)
    {
        Message = new byte[4 + MaskLength + Length];
        Message[1] = (byte)126;
        Message[2] = (byte)(Length / 256);
        Message[3] = (byte)(Length % 256);
    }
    else
    {
        Message = new byte[10 + MaskLength + Length];
        Message[1] = (byte)127;

        int left = Length;
        int unit = 256;

        for (int i = 9; i > 1; i--)
        {
            Message[i] = (byte)(left % unit);
            left = left / unit;

            if (left == 0)
                break;
        }
    }

    //Set FIN
    Message[0] = (byte)129;// (0 | 0x80);

    //Set mask bit
    //Message[1] = (byte)(Message[1] | 0x80);

    //GenerateMask(Message, Message.Length - MaskLength - Length);

    //if (Length > 0)
        //MaskData(Payload, 0, Length, Message, Message.Length - Length, Message, Message.Length - MaskLength - Length);

    char[] output = new char[Message.Length-4];

    for( int i = 0, y = 0, z = 0; i < Message.Length; i++ )
    {
        if (Message[z] == '\0')
        {
            if (Payload.Length > i-z)
                output[i] = (char)Payload[y++];
        }
        else
        {
            output[i] = (char)Message[z++];
        }
    }

    this.OutputWriter.Write(output, 0, output.Length);
    this.OutputWriter.Flush();
}

更新:我刚刚用我最新的替换了本文档中的所有代码。

总结一下:

- The client-server handshake has been matched on both sides.
- A path has been defined in the URI for the WebSocket.
- Data packets are now being 'properly' framed.

我在编辑时才注意到的一点是我的 WriteMessage 方法的最后几行。完成所有框架后,我将字节数组转换为字符数组并使用 StreamReader.Write 发送它。我不确定这是否是一个可行的解决方案,所以如果不是,请让我检查一下。

否则我很困惑。这似乎符合我读过的所有标准,但仍然让我失望。如果我能让它发挥作用,这就是交易的制定者,所以我真的很感谢任何人的帮助。

谢谢你。-DigitalJedi掌心

4

1 回答 1

1

该问题是由于在握手响应中使用了 Sec-WebSocket-Protocol 引起的。客户端没有请求子协议,因此来自服务器的唯一有效响应是在不指定子协议的情况下完成握手。如果服务器以意外的子协议响应,则要求客户端关闭连接。有关详细信息,请参阅RFC 6455的第 4.2.2 节中的 /subprotocol/ 部分。

最简单的解决方法是从您的响应中删除 Sec-WebSocket-Protocol 标头。如果要保留它,则需要将子协议名称作为第二个参数传递给客户端的 WebSocket 构造函数,并在服务器的响应中使用此子协议。有关详细信息,请参阅客户端 API文档。

编辑:
完成握手后,服务器很可能无法尝试从客户端的 onOpen 读取“TestOutput”消息。WebSocket 消息不是纯文本并且不使用 HTTP,因此该行this.ReadLine()极不可能找到一个 \r\n 来终止。有关详细信息,请参阅规范的数据框架部分。这篇wiki 帖子有一些用于 websocket 读/写的有用伪代码。或者,您可以试试我的C++ 服务器。请参阅WsProtocol80::Read()如何阅读消息。或者查看其中一种开源 C# 服务器,例如Fleck(读取/写入消息的代码已链接)。

您可以考虑其他一些小的更改,这些更改将使您的代码更加健壮,但不会影响立即通过和失败:

  • 理想情况下,您指定的任何子协议都应包含您的域名,以最大程度地减少意外匹配任何不兼容协议请求的机会。RFC 6455的早期部分解释了原因。
  • 在使用支持的子协议进行响应之前,值得考虑检查请求中是否存在 Sec-WebSocket-Protocol 标头及其值。
  • 无法保证客户端请求中标头的顺序,因此您可能会延迟响应,直到您读取空行。
于 2012-04-17T08:48:25.723 回答