WebSocket连接在onmessage之前丢弃

时间:2012-04-06 22:28:29

标签: c# sockets websocket streamwriter tcplistener

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

对于这种情况真正令人困惑的是,在传输“握手”期间,文本可以双向传输,并且在每行发送后执行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 facepalm

1 个答案:

答案 0 :(得分:1)

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

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

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

您可以考虑进行一些其他小的更改,这些更改会使您的代码更加强大,但不会在即时通过和失败之间产生差异:

  • 您指定的任何子协议理想情况下都应包含您的域名,以尽量减少意外匹配任何不兼容的协议请求的可能性。 RFC 6455的early section解释了原因。
  • 在使用支持的子协议进行响应之前,请务必考虑检查请求中Sec-WebSocket-Protocol标头的存在和值。
  • 无法保证客户端请求中的标头顺序,因此您可以延迟响应,直至读取空行。