Introduction détaillée à Websocket en php

黄舟
Libérer: 2023-03-16 09:12:01
original
1691 Les gens l'ont consulté

Ci-dessous, j'ai dessiné une image pour illustrer la partie poignée de main lors de l'établissement d'une connexion websocket entre le client et le serveur. Cette partie peut être complétée très facilement dans node, car le module net fourni par node a déjà encapsulé le socket lorsque les développeurs l'utilisent. , ils n'ont qu'à prendre en compte l'interaction des données et n'ont pas besoin de s'occuper de l'établissement des connexions. Cependant, PHP ne le fait pas. Depuis la connexion du socket, l'établissement, la liaison, la surveillance, etc., nous devons les gérer nous-mêmes, il est donc nécessaire de les retirer et d'en parler.


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

Les étudiants qui ont lu le dernier article que j'ai écrit devraient avoir une compréhension plus complète de l'image ci-dessus. ① et ② sont en fait une requête et une réponse HTTP, mais ce que nous obtenons pendant le traitement est une chaîne non analysée. Par exemple :


GET /chat HTTP/1.1Host: server.example.com
Origin: http://example.com
Copier après la connexion

La requête que nous voyons habituellement ressemble à ceci. Lorsque cette chose atteint le serveur, nous pouvons obtenir ces informations directement via certaines bibliothèques de codes.

1. Traitement du websocket en php

La connexion WebSocket est activement initiée par le client, donc tout doit commencer par le client. La première étape consiste à analyser la chaîne Sec-WebSocket-Key envoyée par le client.


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
Copier après la connexion

Le format de la requête client est également mentionné dans l'article précédent (comme ci-dessus). Tout d'abord, php établit une connexion socket et surveille les informations sur le port.

1. Établissement d'une connexion socket

Concernant l'établissement d'une prise, je pense que beaucoup de personnes qui ont étudié les réseaux informatiques à l'université le savent. Ce qui suit est une image du processus d'établissement d'une connexion. :


// 建立一个 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);
Copier après la connexion

Par rapport au nœud, le traitement de cet endroit est vraiment gênant. Les lignes de code ci-dessus n'établissent pas de. connexion, mais ces codes sont ce qui doit être écrit pour établir une prise. Le processus de traitement étant légèrement compliqué, j'ai écrit divers processus dans une classe pour faciliter la gestion et les appels.

//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";
                    }
                }
            }
        }
    }
}
Copier après la connexion

Le code ci-dessus a été débogué par moi et il n'y a pas de gros problème si vous souhaitez le tester, vous pouvez taper php /path/to/demo.php dans la ligne de commande cmd ; Bien sûr, ce qui précède n'est qu'une classe. Si vous souhaitez le tester, vous devez créer une nouvelle instance.


$ws = new WS(&#39;localhost&#39;, 4000);
Copier après la connexion

Le code client peut être légèrement plus simple :


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

Exécutez le code du serveur lorsque le client Lors de la connexion, nous pouvons voir :

Grâce au code ci-dessus, nous pouvons voir clairement l'ensemble du processus de communication. La première consiste à établir une connexion. Cette étape du nœud a été encapsulée dans les modules net et http, puis détermine s'il faut serrer la main. Sinon, shakeHands. Pour la poignée de main ici, j'ai directement fait écho à un mot pour indiquer que cette chose a été effectuée. Nous avons mentionné l'algorithme de poignée de main plus tôt, je l'ai donc écrit directement ici.

2. Extraire les informations Sec-WebSocket-Key


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

Il s'agit d'une correspondance régulière relativement simple et directe, l'en-tête des informations Websocket doit contenir Sec- WebSocket- Key, afin que nous puissions le faire correspondre plus rapidement ~

3. Chiffrer Sec-WebSocket-Key


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));
}
Copier après la connexion

Les caractères cryptés SHA-1 Le la chaîne est à nouveau chiffrée en base64. Si l'algorithme de cryptage est erroné, le client signalera directement une erreur lors de la vérification :

4. Répondre à 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;
}
Copier après la connexion

Assurez-vous de faire attention ici. Chaque demande et format correspondant a une ligne vide à la fin, ce qui est rn J'ai perdu cette chose lorsque j'ai commencé à tester et j'ai eu du mal avec elle pendant longtemps. temps.

Lorsque le client vérifie avec succès la clé, la fonction onopen sera déclenchée :

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;
}
Copier après la connexion

Les problèmes d'encodage impliqués ici ont été mentionnés dans l'article précédent, je n'entrerai donc pas dans les détails ici. PHP a trop de fonctions pour le traitement des caractères, donc je me souviens que ce n'est pas spécial. Évidemment, il n'y a pas d'introduction détaillée au programme de décodage ici. Les données envoyées par le client sont directement renvoyées telles quelles.


// 返回帧信息处理
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));
}
Copier après la connexion

Code client :


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("李靖");
Copier après la connexion

Envoyer les données après la connexion, et le serveur renvoie tel quel :

2. Attention problèmes

1. Problème de version Websocket

Le client est dans la requête pendant poignée de main Il existe Sec-WebSocket-Version: 13, une telle marque de version. Il s'agit d'une version mise à niveau et tous les navigateurs actuels utilisent cette version. La version précédente était plus gênante dans la partie cryptage des données. Elle envoyait deux clés :


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
Copier après la connexion

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


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);
}
Copier après la connexion

只能无限吐槽这种验证方式!相比 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);
Copier après la connexion

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal