0

サーバーに接続して C# と握手する Javascript で WebSocket クライアントを作成しました。現在、出力ストリームを「フラッシュ」すると (クライアントにヘッダー BACK を送信し、接続を確認した後でのみ)、クライアントがクラッシュし、次回書き込みを試みるとサーバー側の例外が生成されます。後者の動作が予想されますが、私が理解できないのは、フラッシュが接続を切断する理由です。私は TcpListener と Socket を使用しており、サーバー側では StreamWriter を使用し、クライアントではプレーンな 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.

これを編集しているときに気付いた 1 つのポイントは、WriteMessage メソッドの最後の数行です。すべてのフレーミングを行った後、バイト配列を文字配列に変換し、StreamReader.Write を使用して送信します。これが実行可能な解決策であるかどうかはわかりませんので、そうでない場合はチェックしてください。

そうでなければ、私は当惑します。これは、私が読んだことのあるすべての標準に準拠しているように見えますが、それでも悲惨なことに失敗します。私がそれを機能させることができれば、これはかなりの取引メーカーですので、誰の助けにも本当に感謝しています.

ありがとうございました。-DigitalJediフェイスパーム

4

1 に答える 1

1

この問題は、ハンドシェイク応答での Sec-WebSocket-Protocol の使用によって引き起こされます。クライアントはサブプロトコルを要求しなかったため、サーバーからの唯一の有効な応答は、サブプロトコルを指定せずにハンドシェイクを完了することです。サーバーが予期しないサブプロトコルで応答した場合、クライアントは接続を閉じる必要があります。詳細については、 RFC 6455のセクション 4.2.2 の /subprotocol/ セクションを参照してください。

最も簡単な修正方法は、応答から Sec-WebSocket-Protocol ヘッダーを削除することです。保持したい場合は、サブプロトコル名を 2 番目の引数としてクライアントの WebSocket コンストラクターに渡し、このサブプロトコルをサーバーの応答で使用する必要があります。詳細については、クライアント APIドキュメントを参照してください。

編集:
ハンドシェイクが完了すると、サーバーはクライアントの onOpen から「TestOutput」メッセージを読み取ろうとして失敗する可能性が非常に高くなります。WebSocket メッセージはプレーン テキストではなく、HTTP を使用しないため、行this.ReadLine()が終了する \r\n を見つける可能性はほとんどありません。詳細については、仕様のデータ フレーミングセクションを参照してください。このwiki 投稿には、websocket の読み取り/書き込みに役立つ疑似コードがいくつか含まれています。または、私のC++ サーバーを試すこともできます。WsProtocol80::Read()メッセージの読み方については、 を参照してください。または、 Fleckなどのオープン ソース C# サーバーの 1 つを調べてください(メッセージを読み書きするコードがリンクされています)。

コードをより堅牢にするために考慮できるその他の小さな変更がいくつかありますが、即時の合格と不合格の違いはありません。

  • 指定するサブプロトコルには、互換性のないプロトコル要求に誤って一致する可能性を最小限に抑えるために、理想的にはドメイン名を含める必要があります。RFC 6455の初期のセクションで、その理由が説明されています。
  • サポートされているサブプロトコルで応答する前に、リクエスト内の Sec-WebSocket-Protocol ヘッダーの存在と値を確認することを検討する価値があります。
  • クライアント要求のヘッダーの順序は保証されていないため、空の行を読み取るまで応答が遅れる可能性があります。
于 2012-04-17T08:48:25.723 に答える