【问题标题】:WebSocket Connection Drops before onmessage在 onmessage 之前 WebSocket 连接断开
【发布时间】:2012-04-20 10:53:44
【问题描述】:

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

这个场景真正令人困惑的是,在“握手”的传输过程中,文本可以双向传输,每行发送后都会执行一次刷新,但一旦握手完成,刷新就会终止连接。

如果本次修订信息不足,请告诉我;因为它现在已经修改了几次。

提前致谢。

客户端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

【问题讨论】:

  • 所以你写了客户端和服务器端?你能提供代码或你执行什么样的握手以及你是如何做到的?
  • 当然 - 我现在就编辑它。
  • 如果里面有什么需要改进的,请告诉我。
  • TcpListener 呢?你赶上了一个新的传入连接,接收新的 tcpclient ......你在一个新的线程中这样做?那么try and catch 怎么样,你向流询问数据......当客户端拒绝连接时,你是如何处理它的?你在哪里计算和计算客户端的握手?
  • 如果你愿意,我可以粘贴连接器代码,但监听器有自己的线程,每个新连接都有自己的线程。我确实在接受代码中尝试/捕获,但还没有遇到异常。根据客户端,在实例化对象时发送握手,并且一旦我收到握手的最后一行,我就会传回我的响应。我将发布另一个编辑。

标签: c# sockets websocket streamwriter tcplistener


【解决方案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 标头及其值。
  • 无法保证客户端请求中标头的顺序,因此您可能会延迟响应,直到您读取空行。

【讨论】:

  • 可能是我的最终解决方案。我将使用它并将其作为答案传递。谢谢你。这可能让我很头疼。
  • 可悲的是 - 即使在注释掉我的回复后,我仍然以相同的结果结束。如果您没有任何进一步的建议,我可能会将此作为答案,因为它似乎与我将要得到的一样接近,但遗憾的是,我仍然在同一条船上。跨度>
  • @DigitalJedi805 啊,我刚刚发现了另一个问题。我会在几秒钟内更新我的答案
  • 非常感谢西蒙。我会对此进行研究,并确保让您知道它是如何进行的。我不确定在这个赏金到期之前我是否有机会,所以它是你的;多陪我几天,以防我没有确定下来;如果你不介意的话。
  • 嘿西蒙,我已经按照规定进行了一些更改,但仍然没有成功。在这一点上的任何建议都会很棒。不管怎样,你已经帮了大忙了。我修改了原始帖子的大部分内容以包含更改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-20
  • 1970-01-01
  • 1970-01-01
  • 2016-08-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多