1. ホーム
  2. c#

[解決済み] Hello World" WebSocketのサンプルを作成する

2023-02-12 14:54:07

質問

以下のコードが動作しない理由がわかりません。私はJavaScriptで私のサーバーコンソールアプリケーションに接続したい。そして、サーバーにデータを送信します。

以下はサーバーのコードです。

    static void Main(string[] args)
    {            
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 9998);
        server.Start();
        var client = server.AcceptTcpClient();
        var stream = client.GetStream();

        while (true)
        {
            var buffer = new byte[1024]; 
            // wait for data to be received
            var bytesRead = stream.Read(buffer, 0, buffer.Length);                
            var r = System.Text.Encoding.UTF8.GetString(buffer);
            // write received data to the console
            Console.WriteLine(r.Substring(0, bytesRead));
        }
    }

で、以下がそのJavaScriptです。

        var ws = new WebSocket("ws://localhost:9998/service");
        ws.onopen = function () {
            ws.send("Hello World"); // I WANT TO SEND THIS MESSAGE TO THE SERVER!!!!!!!!
        };

        ws.onmessage = function (evt) {
            var received_msg = evt.data;
            alert("Message is received...");
        };
        ws.onclose = function () {
            // websocket is closed.
            alert("Connection is closed...");
        };

このコードを実行すると、このようになります。

JavaScriptを実行すると、サーバーは接続を受け入れ、正常に確立されることに注意してください。しかし、JavaScriptはデータを送信することができません。送信メソッドを配置すると、接続が確立されているにもかかわらず、送信されません。どうしたらうまくいくでしょうか?

どうすれば解決しますか?

WebSocket は TCP ストリーム接続に依存するプロトコルです。WebSocketsはメッセージベースのプロトコルです。

もし、独自のプロトコルを実装したいのであれば、最新の安定した仕様(18/04/12版)を使用することをお勧めします。 RFC 6455 . この仕様には、ハンドシェイクとフレーミングに関するすべての必要な情報が含まれています。また、ブラウザ側とサーバ側での動作のシナリオに関する記述も多くあります。 サーバ側については、この勧告に従った実装をすることを強く推奨します。

WebSocketの使い方を一言で言うと、こんな感じです。

  1. サーバーソケットの作成 (System.Net.Sockets) を特定のポートにバインドし、非同期で接続を受け付け、リッスンし続ける。といったところでしょうか。
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP).Socket(System.Net.Socket)を作成します。
serverSocket.Bind(新しいIPEndPoint(IPAddress.Any, 8080))を実行します。
serverSocket.Listen(128)を実行します。
serverSocket.BeginAccept(null, 0, OnAccept, null);
  1. を用意する必要があります。 受理 関数 "OnAccept"でハンドシェイクを実装してください。将来的には、もしシステムが1秒間に大量の接続を処理することを意図しているならば、それは別のスレッドでなければなりません。
private void OnAccept(IAsyncResult result) {
    try {
        Socket client = null;
        if (serverSocket != null && serverSocket.IsBound){。
            client = serverSocket.EndAccept(result)。
        }
        if (client != null) { /* ハンドシェイクとクライアント管理
            /* ClientSocketのハンドシェイクと管理 */
        }
    } catch(SocketExceptionの例外) { } final { } catch(SocketExceptionの例外)

    } finally {
        if (serverSocket != null && serverSocket.IsBound){。
            serverSocket.BeginAccept(null, 0, OnAccept, null);
        }
    }
}
  1. 接続が確立したら ハンドシェイク . 仕様に基づき 1.3 オープニングハンドシェイク に基づいて、接続が確立されると、いくつかの情報を含む基本的な HTTP リクエストを受け取ります。例
GET /chat HTTP/1.1
ホスト:server.example.com
アップグレード:ウェブソケット
接続 アップグレード
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
オリジン: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

この例は、プロトコル 13 のバージョンに基づいています。古いバージョンではいくつかの違いがありますが、ほとんどの最新バージョンは相互互換性があることに留意してください。ブラウザによっては、いくつかの追加データを送信することがあります。たとえば、ブラウザーと OS の詳細、キャッシュ、その他です。

これらはほとんど同じですが、提供された Sec-WebSocket-Key に基づいた Accept-Key が含まれます。仕様書1.3では、レスポンスキーを生成する方法が明確に記述されています。 以下は、私がV13で使っていた関数です。

static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
プライベート文字列AcceptKey(ref文字列key){(ref文字列guid)
        文字列 longKey = key + guid;
        SHA1 sha1 = SHA1CryptoServiceProvider.Create();
        byte[] hashBytes = sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(longKey)).Byte[] hashBytes = sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(longKey));
        return Convert.ToBase64String(hashBytes);
}


握手会の答えはこんな感じです。

HTTP/1.1 101 プロトコルの切り替え
アップグレード:ウェブソケット
接続する アップグレード
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

ただし、acceptキーは、クライアントから提供されたキーと、私が前に提供したAcceptKeyメソッドに基づいて生成されたものである必要があります。また、accept keyの最後の文字の後に、" \rnrn" と2行入れてください。

  1. サーバーからハンドシェイクアンサーが送信された後、クライアントは"をトリガーする必要があります。 onopen 関数を呼び出す必要があります。

  2. メッセージは生のまま送信されませんが、メッセージには データ・フレーミング . そして、クライアントからサーバへも、メッセージヘッダの4バイトを元にデータのマスキングを実装します。ただし、サーバからクライアントへはデータに対するマスキングは必要ありません。セクションを読む 5. データフレーミング を読んでください。 以下は、私自身の実装からのコピーペーストです。すぐに使えるコードではないので、修正が必要ですが、WebSocketフレームを使った読み書きの全体的なロジックを理解するために掲載しています。次のページに移動します。 このリンク .

  3. フレーミングを実装した後は、ソケットを使って正しい方法でデータを受信できるようにします。例えば、TCPはまだストリームベースのプロトコルなので、いくつかのメッセージが1つに統合されるのを防ぐためです。これは、特定のバイト数だけ読まなければならないことを意味します。メッセージの長さは、常にヘッダとそれ自身のヘッダで提供されるデータの長さの詳細に基づいています。つまり、ソケットからデータを受信する場合、まず2バイトを受信し、フレーミング仕様に基づいてヘッダーから詳細を取得し、マスクが提供された場合はさらに4バイト、そしてデータの長さに基づいて1、4または8バイトであるかもしれません。そして、データの後に自己のデータ。読み込んだら、デマスキングを適用すれば、メッセージデータは使えるようになります。

  4. を使いたいかもしれませんが、いくつかの データプロトコル がありますが、クライアント側でJavaScriptを使う場合はトラフィックの観点からJSONを使うことをお勧めします。サーバーサイドでは、いくつかのパーサーをチェックしたいかもしれません。それらの多くは、Googleは本当に役に立つことができます。

独自のWebSocketプロトコルを実装することは、間違いなくいくつかの利点と、プロトコルそれ自体の制御と同様に得られる素晴らしい経験を持っています。しかし、あなたはそれを行うにはいくつかの時間を費やし、その実装が高い信頼性を持つことを確認する必要があります。

同時に、あなたは、Googleが(再び)十分に持っているすぐに使用できるソリューションを見ることができるかもしれません。