簡體   English   中英

WebSocket onmessage 之前連接斷開

[英]WebSocket Connection Drops before onmessage

我在 Javascript 中編寫了一個 WebSocket 客戶端,它與我的服務器 C# 連接並握手。 目前,在“刷新”我的 output stream 時(僅在我將 header 發送回客戶端並確認我的連接之后),當我下次嘗試寫入時,客戶端崩潰並生成服務器端異常。 后一種行為是預期的,但是我不明白為什么沖洗會斷開連接。 我在服務器端使用 TcpListener 和帶有 StreamWriter 的套接字,在客戶端使用普通的 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;
        }
    }
}

發送我的 Header 回復:

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");
}

並從客戶端獲取以下 output(此時):

#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

消息代碼:-取自我在 .net 上找到的東西,稍作修改......

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

問題是由在握手響應中使用 Sec-WebSocket-Protocol 引起的。 客戶端沒有請求子協議,因此來自服務器的唯一有效響應是在不指定子協議的情況下完成握手。 如果服務器以意外的子協議響應,則客戶端需要關閉連接。 有關詳細信息,請參閱RFC 6455第 4.2.2 節中的 /subprotocol/ 部分。

最簡單的解決方法是從您的響應中刪除 Sec-WebSocket-Protocol header。 如果你想保留它,你需要將一個子協議名稱作為第二個參數傳遞給客戶端的 WebSocket 構造函數,並在你的服務器的響應中使用這個子協議。 有關詳細信息,請參閱客戶端 API文檔。

編輯:
完成握手后,服務器很可能無法嘗試從客戶端的 onOpen 讀取“TestOutput”消息。 WebSocket 消息不是純文本並且不使用 HTTP 因此行this.ReadLine()極不可能找到 \r\n 終止。 有關詳細信息,請參閱規范的數據幀部分。 This wiki post有一些有用的偽代碼,用於 websocket 讀/寫。 或者,您可以試試我的C++ 服務器 請參閱WsProtocol80::Read()了解如何讀取消息。 或者查看其中一個開源服務器 C#,例如Fleck (已鏈接讀取/寫入消息的代碼)。

您可以考慮其他一些小的更改,這些更改會使您的代碼更健壯,但不會在立即通過和失敗之間產生區別:

  • 您指定的任何子協議最好包括您的域名,以盡量減少意外匹配任何不兼容協議請求的可能性。 RFC 6455 的早期部分解釋了原因。
  • 在使用支持的子協議進行響應之前,值得考慮檢查請求中是否存在 Sec-WebSocket-Protocol header 及其值。
  • 無法保證客戶端請求中標頭的順序,因此您可以延遲響應,直到讀取空行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM