Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Einführung in WebSocket in PHP

Detaillierte Einführung in WebSocket in PHP

黄舟
黄舟Original
2017-09-18 10:46:511698Durchsuche

Unten habe ich ein Bild gezeichnet, um den Handshake-Teil beim Herstellen einer Websocket-Verbindung zwischen Client und Server zu demonstrieren. Dieser Teil kann sehr einfach im Knoten ausgeführt werden, da das vom Knoten bereitgestellte Netzmodul den Socket bereits gekapselt hat Sie müssen nur die Interaktion von Daten berücksichtigen und müssen sich nicht mit dem Aufbau von Verbindungen befassen. Bei PHP ist dies jedoch nicht der Fall. Wir müssen dies selbst tun, was die Socket-Verbindung, den Aufbau, die Bindung, die Überwachung usw. betrifft, daher ist es notwendig, es herauszunehmen und darüber zu sprechen.


    +--------+    1.发送Sec-WebSocket-Key        +---------+
    |        | --------------------------------> |        |
    |        |    2.加密返回Sec-WebSocket-Accept  |        |
    | client | <-------------------------------- | server |
    |        |    3.本地校验                      |        |
    |        | --------------------------------> |        |
    +--------+                                   +--------+

Schüler, die den letzten Artikel gelesen haben, den ich geschrieben habe, sollten ein umfassenderes Verständnis des obigen Bildes haben. ① und ② sind eigentlich eine HTTP-Anfrage und -Antwort, aber was wir während der Verarbeitung erhalten, ist eine nicht geparste Zeichenfolge. Zum Beispiel:


GET /chat HTTP/1.1Host: server.example.com
Origin: http://example.com

Die Anfrage, die wir normalerweise sehen, sieht so aus. Wenn dieses Ding den Server erreicht, können wir diese Informationen direkt über einige Codebibliotheken abrufen.

1. WebSocket in PHP verarbeiten

Die WebSocket-Verbindung wird aktiv vom Client initiiert, daher muss alles vom Client ausgehen. Der erste Schritt besteht darin, die vom Client gesendete Sec-WebSocket-Key-Zeichenfolge zu analysieren.


GET /chat HTTP/1.1Host: server.example.com
Upgrade: websocket
Connection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

Das Format der Client-Anfrage wurde auch im vorherigen Artikel erwähnt (wie oben). Zuerst stellt PHP eine Socket-Verbindung her und überwacht die Portinformationen.

1. Herstellung einer Socket-Verbindung

Ich glaube, dass viele Leute, die an der Hochschule Computernetzwerke studiert haben, dies wissen :


// 建立一个 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);

Im Vergleich zum Knoten ist die Verarbeitung dieses Ortes wirklich mühsam. Die obigen Codezeilen stellen keinen dar Verbindung, aber diese Codes müssen geschrieben werden, um einen Socket herzustellen. Da der Verarbeitungsprozess etwas kompliziert ist, habe ich verschiedene Prozesse in eine Klasse geschrieben, um die Verwaltung und den Anruf zu erleichtern.

//demo.php
Class 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";
                    }
                }
            }
        }
    }
}

Der obige Code wurde von mir debuggt und es gibt kein großes Problem. Wenn Sie ihn testen möchten, können Sie php /path/to/demo.php in die cmd-Befehlszeile eingeben Natürlich ist das Obige nur eine Klasse. Wenn Sie es testen möchten, müssen Sie eine neue Instanz erstellen.


$ws = new WS(&#39;localhost&#39;, 4000);

Der Client-Code kann etwas einfacher sein:


var ws = new WebSocket("ws://localhost:4000");
ws.onopen = function(){
    console.log("握手成功");
};
ws.onerror = function(){
    console.log("error");
};

Führen Sie den Servercode aus, wenn der Client Beim Herstellen einer Verbindung können wir Folgendes sehen:

Durch den obigen Code können wir den gesamten Kommunikationsprozess klar sehen. Der erste Schritt besteht darin, eine Verbindung im Netz- und HTTP-Modul herzustellen und dann zu bestimmen, ob die Hand geschüttelt werden soll. Für den Handshake hier habe ich direkt ein Wort wiederholt, um anzuzeigen, dass diese Sache ausgeführt wurde. Wir haben den Handshake-Algorithmus bereits erwähnt, also habe ich ihn direkt hier geschrieben.

2. Sec-WebSocket-Key-Informationen extrahieren


function getKey($req) {    
$key = null;    
if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req, $match)) { 
        $key = $match[1]; 
    }    return $key;
}

Dies ist ein relativ einfacher, direkter regulärer Abgleich. Der Websocket-Informationsheader muss Sec- enthalten. WebSocket-Schlüssel, damit wir ihn schneller abgleichen können~

3. Verschlüsseln Sie den Sec-WebSocket-Schlüssel


function encry($req){    
$key = $this->getKey($req);    
$mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";    
return base64_encode(sha1($key . &#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#39;, true));
}

Die SHA-1-verschlüsselten Zeichen string ist wieder Base64-verschlüsselt. Wenn der Verschlüsselungsalgorithmus falsch ist, meldet der Client bei der Überprüfung direkt einen Fehler:

4. Reagieren Sie auf Sec-WebSocket-Accept


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

Achten Sie hier darauf, dass am Ende jede Anfrage und jedes entsprechende Format eine Leerzeile enthält. rn Ich habe dieses Ding verloren, als ich angefangen habe, es zu testen Zeit.

Wenn der Client den Schlüssel erfolgreich überprüft, wird die Onopen-Funktion ausgelöst:

5


// 解析数据帧
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;
}

Die hier auftretenden Codierungsprobleme wurden im vorherigen Artikel erwähnt, daher werde ich hier nicht auf Details eingehen. PHP verfügt über zu viele Funktionen für die Zeichenverarbeitung. Ich erinnere mich also, dass es nichts Besonderes ist. Offensichtlich gibt es hier keine detaillierte Einführung in das Decodierungsprogramm. Die vom Client gesendeten Daten werden direkt zurückgegeben, was als Chatroom-Modus angesehen werden kann.


// 返回帧信息处理
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));
}

Client-Code:


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("李靖");

Daten nach der Verbindung senden, und der Server kehrt unverändert zurück :

2. Achtung Probleme

1. Websocket-Versionsproblem

Der Client befindet sich während der Anfrage Handshake Es gibt Sec-WebSocket-Version: 13, eine solche Versionsmarkierung. Dies ist eine aktualisierte Version, und alle aktuellen Browser verwenden diese Version. Die vorherige Version war im Datenverschlüsselungsteil problematischer. Sie sendete zwei Schlüssel:


GET /chat HTTP/1.1Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchatSec-WebSocket-Key1: xxxxSec-WebSocket-Key2: xxxx

如果是这种版本(比较老,已经没在使用了),需要通过下面的方式获取


function encry($key1,$key2,$l8b){ //Get the numbers preg_match_all(&#39;/([\d]+)/&#39;, $key1, $key1_num); preg_match_all(&#39;/([\d]+)/&#39;, $key2, $key2_num);    
$key1_num = implode($key1_num[0]);    
$key2_num = implode($key2_num[0]);    
//Count spaces    
preg_match_all(&#39;/([ ]+)/&#39;, $key1, $key1_spc);    
preg_match_all(&#39;/([ ]+)/&#39;, $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);
}

只能无限吐槽这种验证方式!相比 nodeJs 的 websocket 操作方式:


//服务器程序var crypto = require(&#39;crypto&#39;);var WS = &#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#39;;
require(&#39;net&#39;).createServer(function(o){    var key;
    o.on(&#39;data&#39;,function(e){        if(!key){            //握手
            key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
            key = crypto.createHash(&#39;sha1&#39;).update(key + WS).digest(&#39;base64&#39;);
            o.write(&#39;HTTP/1.1 101 Switching Protocols\r\n&#39;);
            o.write(&#39;Upgrade: websocket\r\n&#39;);
            o.write(&#39;Connection: Upgrade\r\n&#39;);
            o.write(&#39;Sec-WebSocket-Accept: &#39; + key + &#39;\r\n&#39;);
            o.write(&#39;\r\n&#39;);
        }else{
            console.log(e);
        };
    });
}).listen(8000);

多么简洁,多么方便!有谁还愿意使用 php 呢。。。。

Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in WebSocket in PHP. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn