以下に、クライアントとサーバー間の 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