Heim  >  Artikel  >  Backend-Entwicklung  >  PHP-Artikeldetails WebSocket

PHP-Artikeldetails WebSocket

coldplay.xixi
coldplay.xixinach vorne
2020-06-16 17:19:373215Durchsuche

PHP-Artikeldetails WebSocket

Unten habe ich ein Diagramm gezeichnet, um den Handshake-Teil beim Herstellen einer Websocket-Verbindung zwischen Client und Server zu demonstrieren. Dieser Teil kann sehr einfach in Node abgeschlossen werden, da das von Node bereitgestellte Netzmodul vorhanden ist Bereits Socket-Sockets sind gekapselt. Bei ihrer Verwendung müssen Entwickler nur die Interaktion von Daten berücksichtigen, ohne sich mit dem Aufbau von Verbindungen befassen zu müssen. 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.1
Host: 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. Verarbeitung des WebSockets in PHP

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

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

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

1. Herstellung einer Socket-Verbindung

Was den Aufbau einer Socket betrifft, glaube ich, 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 keine Verbindung her, aber diese Codes sind zum Herstellen eines Sockets erforderlich . Etwas, worüber man schreiben kann. 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";
                    }
                }
            }
        }
    }
}

demo.php Handshake-Verbindungstestcode

Der obige Code wurde von mir debuggt, nein, er ist groß Problem. Wenn Sie es testen möchten, können Sie natürlich php /path/to/demo.php in die Befehlszeile eingeben. Wenn Sie es testen möchten, müssen Sie eine neue Instanz erstellen.

$ws = new WS('localhost', 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");
};

Wenn wir den Servercode ausführen und der Client eine Verbindung herstellt, sehen wir:

Sie können den gesamten Kommunikationsprozess anhand des obigen Codes deutlich 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-WebSocket-Key enthalten, damit wir ihn schneller abgleichen können~

3. Sec-WebSocket-Key verschlüsseln

function encry($req){
    $key = $this->getKey($req);
    $mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

    return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
}

Verschlüsseln Sie die SHA-1-verschlüsselte Zeichenfolge erneut mit base64. Wenn der Verschlüsselungsalgorithmus falsch ist, meldet der Client beim Überprüfen direkt einen Fehler:

4. Antwort 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;
}

Zehn Millionen hier Es ist zu beachten, dass am Ende jeder Anfrage und des entsprechenden Formats eine Leerzeile steht, nämlich rn. Diese habe ich verloren, als ich mit dem Testen begonnen habe, und hatte lange Zeit damit zu kämpfen.

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

这里涉及的编码问题在前文中已经提到过了,这里就不赘述,php 对字符处理的函数太多了,也记得不是特别清楚,这里就没有详细的介绍解码程序,直接把客户端发送的数据原样返回,可以算是一个聊天室的模式吧。

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

客户端代码:

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

在连通之后发送数据,服务器原样返回:

 

二、注意问题

1. websocket 版本问题

客户端在握手时的请求中有Sec-WebSocket-Version: 13,这样的版本标识,这个是一个升级版本,现在的浏览器都是使用的这个版本。而以前的版本在数据加密的部分更加麻烦,它会发送两个key:

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

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

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

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

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

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

2. 数据帧解析代码

本文没有给出 decodeFrame 这样数据帧解析代码,前文中给出了数据帧的格式,解析纯属体力活。

3. 代码下载

对这部分感兴趣的同学,可以再去深究。提供了参考代码下载。

4. 相关开源库参考

socketo.me Ratchet 为 php 封装的一个 WebSockets 库。 ]

Google 上搜索 php+websoket+class,也能找到不少相关的资料。

三、参考资料

  • www.php.net/manual/zh/r… php manual
  • www.rfc-editor.org/rfc/rfc6455…  [RFC6455] WebSocket

推荐教程:《php教程

Das obige ist der detaillierte Inhalt vonPHP-Artikeldetails WebSocket. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.im. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen