WebSocketについて詳しく説明する

WBOY
リリース: 2016-06-23 14:37:51
オリジナル
935 人が閲覧しました

以下に、クライアントとサーバー間の WebSocket 接続を確立するときのハンドシェイク部分を示す図を描きました。これは、ノードによって提供されるネット モジュールが開発時にすでにカプセル化されているため、この部分はノード内で非常に簡単に完了できます。ユーザーはデータの相互作用を考慮するだけでよく、接続の確立に取り組む必要はありません。しかし、PHPはそうではなく、ソケットの接続、確立、バインディング、監視など、これらを自分で操作する必要があるため、それを取り出して話す必要があります。

    +--------+    1.发送Sec-WebSocket-Key        +---------+    |        | --------------------------------> |        |    |        |    2.加密返回Sec-WebSocket-Accept  |        |    | client | <-------------------------------- | server |    |        |    3.本地校验                      |        |    |        | --------------------------------> |        |    +--------+                                   +--------+
ログイン後にコピー

私が書いた前回の記事を読んだ学生は、上の図をより包括的に理解できるはずです。 ①と②は実際にはHTTPリクエストとレスポンスですが、処理中に取得されるのは解析されていない文字列です。例:

GET /chat HTTP/1.1Host: server.example.comOrigin: http://example.com
ログイン後にコピー

私たちが通常目にするリクエストは次のようになります。これがサーバーに到達すると、いくつかのコード ライブラリを通じてこの情報を直接取得できます。

1. PHP で WebSocket を処理する

WebSocket 接続はクライアントによってアクティブに開始されるため、すべてはクライアントから開始する必要があります。最初のステップは、クライアントから送信された Sec-WebSocket-Key 文字列を解析することです。

GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13
ログイン後にコピー

クライアントリクエストのフォーマットについては前回の記事でも触れています(上記の通り) まず、phpはソケット接続を確立し、ポート情報を監視します。

1. ソケット接続の確立

ソケットの確立については、大学でコンピュータネットワークを学んだことがある方はご存知かと思いますが、接続を確立するプロセスは次のとおりです。ここの処理は本当に面倒です。上記のコード行は接続を確立しませんが、ソケットを確立するためにこれらのコードを記述する必要があります。処理プロセスが少し複雑なので、管理や呼び出しを容易にするために、さまざまな処理をクラスに記述しました。

// 建立一个 socket 套接字$master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1);socket_bind($master, $address, $port);socket_listen($master);
ログイン後にコピー

demo.php ハンドシェイク接続テスト コード

上記のコードは私がデバッグしたもので、大きな問題はありません。テストしたい場合は、cmd コマンド ラインで php /path/to と入力できます。 /demo.php; もちろん、上記は単なるクラスです。テストしたい場合は、新しいインスタンスを作成する必要があります。

//demo.phpClass WS {    var $master;  // 连接 server 的 client    var $sockets = array(); // 不同状态的 socket 管理    var $handshake = false; // 判断是否握手    function __construct($address, $port){        // 建立一个 socket 套接字        $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)               or die("socket_create() failed");        socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1)              or die("socket_option() failed");        socket_bind($this->master, $address, $port)                                or die("socket_bind() failed");        socket_listen($this->master, 2)                                           or die("socket_listen() failed");        $this->sockets[] = $this->master;        // debug        echo("Master socket  : ".$this->master."\n");        while(true) {            //自动选择来消息的 socket 如果是握手 自动选择主机            $write = NULL;            $except = NULL;            socket_select($this->sockets, $write, $except, NULL);            foreach ($this->sockets as $socket) {                //连接主机的 client                 if ($socket == $this->master){                    $client = socket_accept($this->master);                    if ($client < 0) {                        // debug                        echo "socket_accept() failed";                        continue;                    } else {                        //connect($client);                        array_push($this->sockets, $client);                        echo "connect client\n";                    }                } else {                    $bytes = @socket_recv($socket,$buffer,2048,0);                    if($bytes == 0) return;                    if (!$this->handshake) {                        // 如果没有握手,先握手回应                        //doHandShake($socket, $buffer);                        echo "shakeHands\n";                    } else {                        // 如果已经握手,直接接受数据,并处理                        $buffer = decode($buffer);                        //process($socket, $buffer);                         echo "send file\n";                    }                }            }        }    }}
ログイン後にコピー

クライアント コードはもう少し単純にすることができます:

$ws = new WS('localhost', 4000);
ログイン後にコピー

サーバー コードを実行します。クライアントが接続すると、次のことがわかります:

上記のコード プロセスを通じて、通信全体を明確に確認できます。 1 つ目は接続を確立することです。ノードのこのステップは net および http モジュールにカプセル化されており、そうでない場合は握手するかどうかを決定します。ここでのハンドシェイクについては、これが実行されたことを示すために単語を直接エコーしました。ハンドシェイクのアルゴリズムについては前述したので、ここに直接書きました。

2. Sec-WebSocket-Key 情報を抽出します

var ws = new WebSocket("ws://localhost:4000");ws.onopen = function(){    console.log("握手成功");};ws.onerror = function(){    console.log("error");};
ログイン後にコピー

これは比較的単純で、直接的な正規の照合です。WebSocket 情報ヘッダーには Sec-WebSocket-Key が含まれている必要があるため、照合が高速になります~

3. Sec-WebSocket-Key を暗号化します

function getKey($req) {    $key = null;    if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req, $match)) {         $key = $match[1];     }    return $key;}
ログイン後にコピー

SHA-1 暗号化文字列を再度 Base64 で暗号化します。暗号化アルゴリズムが間違っている場合、クライアントはチェック時にエラーを直接報告します:

4. Sec-WebSocket-Accept に応答します

function encry($req){    $key = $this->getKey($req);    $mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";    return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));}
ログイン後にコピー

ここでは、各リクエストと対応する形式に必ず注意してください。最後にスペースを入れてOK、つまりrnです。テストを始めたときにこれを紛失してしまい、長い間苦労しました。

クライアントがキーのチェックに成功すると、onopen 関数がトリガーされます:

5. データ フレームの処理

function dohandshake($socket, $req){    // 获取加密key    $acceptKey = $this->encry($req);    $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .               "Upgrade: websocket\r\n" .               "Connection: Upgrade\r\n" .               "Sec-WebSocket-Accept: " . $acceptKey . "\r\n" .               "\r\n";    // 写入socket    socket_write(socket,$upgrade.chr(0), strlen($upgrade.chr(0)));    // 标记握手已经成功,下次接受数据采用数据帧格式    $this->handshake = true;}
ログイン後にコピー

ここで関係するエンコードの問題については、前の記事で説明したため、ここでは説明しません。詳細については、php を参照してください。 文字処理に関する関数が多すぎて、あまり明確に覚えていません。ここでは、デコード プログラムの詳細な説明はありません。クライアントから送信されたデータがそのまま返されます。チャットルームモードとみなされます。

// 解析数据帧function decode($buffer)  {    $len = $masks = $data = $decoded = null;    $len = ord($buffer[1]) & 127;    if ($len === 126)  {        $masks = substr($buffer, 4, 4);        $data = substr($buffer, 8);    } else if ($len === 127)  {        $masks = substr($buffer, 10, 4);        $data = substr($buffer, 14);    } else  {        $masks = substr($buffer, 2, 4);        $data = substr($buffer, 6);    }    for ($index = 0; $index < strlen($data); $index++) {        $decoded .= $data[$index] ^ $masks[$index % 4];    }    return $decoded;}
ログイン後にコピー

クライアント コード:

// 返回帧信息处理function frame($s) {    $a = str_split($s, 125);    if (count($a) == 1) {        return "\x81" . chr(strlen($a[0])) . $a[0];    }    $ns = "";    foreach ($a as $o) {        $ns .= "\x81" . chr(strlen($o)) . $o;    }    return $ns;}// 返回数据function send($client, $msg){    $msg = $this->frame($msg);    socket_write($client, $msg, strlen($msg));}
ログイン後にコピー

接続後にデータを送信すると、サーバーはそのまま戻ります:

2. 問題に注意してください 1. Websocket のバージョンの問題

クライアントのリクエストに Sec- が含まれていますハンドシェイク中の WebSocket-Version: 13 (このようなバージョン識別)、これはアップグレードされたバージョンであり、現在のブラウザはこのバージョンを使用します。以前のバージョンは、データ暗号化の部分でさらに面倒でした:

var ws = new WebSocket("ws://localhost:4000");ws.onopen = function(){    console.log("握手成功");};ws.onmessage = function(e){    console.log("message:" + e.data);};ws.onerror = function(){    console.log("error");};ws.send("李靖");
ログイン後にコピー

このバージョン (古いため使用されていない) の場合は、次の方法で取得する必要があります

GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeOrigin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Key1: xxxxSec-WebSocket-Key2: xxxx
ログイン後にコピー

この検証方法については、際限なく文句を言うしかありません! NodeJs の WebSocket 操作方法と比較すると、

function encry($key1,$key2,$l8b){ //Get the numbers preg_match_all('/([\d]+)/', $key1, $key1_num); preg_match_all('/([\d]+)/', $key2, $key2_num);    $key1_num = implode($key1_num[0]);    $key2_num = implode($key2_num[0]);    //Count spaces    preg_match_all('/([ ]+)/', $key1, $key1_spc);    preg_match_all('/([ ]+)/', $key2, $key2_spc);    if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; }    //Some math    $key1_sec = pack("N",$key1_num / $key1_spc);    $key2_sec = pack("N",$key2_num / $key2_spc);    return md5($key1_sec.$key2_sec.$l8b,1);}
ログイン後にコピー

なんと簡単で便利なのでしょう。まだphpを使いたい人がいるでしょうか? 。 。 。

2. データ フレーム解析コード

この記事では、decodeFrame などのデータ フレーム解析コードは提供しません。データ フレームの形式は、純粋に物理的な作業です。

3. コードのダウンロード

この部分に興味がある学生は、さらに先に進むことができます。参照コードのダウンロードが提供されます。

4. 関連するオープンソース ライブラリのリファレンス

http://socketo.me Ratchet は、php でカプセル化された WebSocket ライブラリです。 】

Googleでphp+websoket+classを検索すると関連情報もたくさん見つかります。

3. 参考資料 http://www.php.net/manual/zh/ref.sockets.php phpマニュアル http://www.rfc-editor.org/rfc/rfc6455.txt [RFC6455] WebSocket

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート