Socket.io 3 和 PHP 集成 [英] Socket.io 3 and PHP integration

查看:31
本文介绍了Socket.io 3 和 PHP 集成的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用 PHP SocketIO 类来连接 NodeJS 应用程序并发送消息.使用 Socket.io 2 一切正常,但升级到版本 3 后,PHP 集成停止工作.

当我发送请求时,我收到此回复:

HTTP/1.1 101 交换协议升级:websocket连接:升级Sec-WebSocket-接受:hNcappwZIQEbMz7ZGWS71lNcROc=

但是我在 NodeJS 端看不到任何东西,即使我尝试使用connection"记录到服务器的任何连接时也是如此.活动.

这是 PHP 类:

class SocketIO{/*** @param null $host - 套接字服务器的 $host* @param null $port - 套接字服务器的端口* @param string $action - 在 sockt 服务器中执行的动作* @param null $data - 到套接字服务器的消息* @param string $address - 套接字服务器上 socket.io 的地址* @param string $transport - 传输类型* @return 布尔值*/public function send($host = null, $port = null, $action= "message", $data = null, $address = "/socket.io/?EIO=2", $transport = 'websocket'){$fd = fsockopen($host, $port, $errno, $errstr);如果 (!$fd) {返回假;}//无法连接到服务器$key = $this->generateKey();$out = "GET $address&transport=$transport HTTP/1.1\r\n";$out.= "Host: https://$host:$port\r\n";$out.= "升级:WebSocket\r\n";$out.= "连接:升级\r\n";$out.= "Sec-WebSocket-Key: $key\r\n";$out.= "Sec-WebSocket-Version: 13\r\n";$out.= "来源:https://$host\r\n\r\n";fwrite($fd, $out);//101切换协议,看是否回显key$result= fread($fd,10000);preg_match('#Sec-WebSocket-Accept:\s(.*)$#mU', $result, $matches);$keyAccept = trim($matches[1]);$expectedResonse = base64_encode(pack('H*', sha1($key .'258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));$handshaked = ($keyAccept === $expectedResonse) ?真假;如果($握手){fwrite($fd, $this->hybi10Encode('42["' . $action . '", "' .addslashes($data) .'"]'));fread($fd,1000000);返回真;} else {返回假;}}私有函数 generateKey($length = 16){$c = 0;$tmp = '';while ($c++ * 16 < $length) { $tmp .= md5(mt_rand(), true);}返回 base64_encode(substr($tmp, 0, $length));}私有函数 hybi10Encode($payload, $type = 'text', $masked = true){$frameHead = array();$payloadLength = strlen($payload);开关($type){案例文本":$frameHead[0] = 129;休息;案例'关闭':$frameHead[0] = 136;休息;案例'平':$frameHead[0] = 137;休息;案例乒乓":$frameHead[0] = 138;休息;}如果 ($payloadLength > 65535) {$payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8);$frameHead[1] = ($masked === true) ?255:127;for ($i = 0; $i <8; $i++) {$frameHead[$i + 2] = bindec($payloadLengthBin[$i]);}如果 ($frameHead[2] > 127) {$this->close(1004);返回假;}} elseif ($payloadLength > 125) {$payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8);$frameHead[1] = ($masked === true) ?254:126;$frameHead[2] = bindec($payloadLengthBin[0]);$frameHead[3] = bindec($payloadLengthBin[1]);} 别的 {$frameHead[1] = ($masked === true) ?$payloadLength + 128 : $payloadLength;}foreach (array_keys($frameHead) as $i) {$frameHead[$i] = chr($frameHead[$i]);}if ($masked === true) {$mask = 数组();for ($i = 0; $i <4; $i++) {$mask[$i] = chr(rand(0, 255));}$frameHead = array_merge($frameHead, $mask);}$frame = implode('', $frameHead);for ($i = 0; $i < $payloadLength; $i++) {$frame .= ($masked === true) ?$payload[$i] ^ $mask[$i % 4] : $payload[$i];}返回 $frame;}}

感谢您的帮助!

解决方案

我在 github 上存在的所有库都遇到了同样的问题,问题是它们被废弃或没有更新到 socket.io V3.

在 socket.io 文档中说:

<块引用>

TL;DR:由于一些重大更改,v2 客户端将无法连接到 v3 服务器(反之亦然)

要解决这个问题,您需要了解 socket.io 客户端的工作原理,这很容易,因为在协议文档中,在示例会话部分.

这是我在节点中的测试服务器

const app = require('express')();const http = require('http').createServer(app);const io = require('socket.io')(http, {核心:{来源:*",方法:[GET",POST"]}});io.on('connection', function (socket) {console.log(新的传输连接",socket.conn.transport.name);socket.on('通知',函数(数据){控制台日志(数据);});});http.listen(3000, () => {console.log('服务器启动端口 3000');});

我需要说,如果您想发送一个方向",这个解决方案非常有效.向您的 socket.io 服务器发送消息,例如新通知或任何不需要永久连接的消息,只是一次性";没有别的.

来自墨西哥的快乐编码和问候.

这是另一个例子:

第一列是 Postman 向 php 服务器发出请求,模拟服务器端事件,就像创建了一个新问题.响应中是您需要发出的 4 个请求的响应正文的转储.

第二列是运行在3000端口的socket.IO节点服务器

最后一列是 chrome 控制台,模拟用户通过 websocket 连接到 socket.IO 服务器,在 'questions' 事件中寻找通知.

I using PHP SocketIO class to connect NodeJS application and send messages. Everything worked wonderfully with Socket.io 2 but after upgrade to version 3 the PHP integration is stopped working.

When I send request I am getting this response:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hNcappwZIQEbMz7ZGWS71lNcROc=

But I don't see anything on NodeJS side, even when I tried to log any connection to the server by using "connection" event.

This is the PHP class:

class SocketIO
{
    /**
     * @param null $host - $host of socket server
     * @param null $port - port of socket server
     * @param string $action - action to execute in sockt server
     * @param null $data - message to socket server
     * @param string $address - addres of socket.io on socket server
     * @param string $transport - transport type
     * @return bool
     */
    public function send($host = null, $port = null, $action= "message",  $data = null, $address = "/socket.io/?EIO=2", $transport = 'websocket')
    {
        $fd = fsockopen($host, $port, $errno, $errstr);
        
        if (!$fd) {
            return false;
        } //Can't connect tot server
        $key = $this->generateKey();
        $out = "GET $address&transport=$transport HTTP/1.1\r\n";
        $out.= "Host: https://$host:$port\r\n";
        $out.= "Upgrade: WebSocket\r\n";
        $out.= "Connection: Upgrade\r\n";
        $out.= "Sec-WebSocket-Key: $key\r\n";
        $out.= "Sec-WebSocket-Version: 13\r\n";
        $out.= "Origin: https://$host\r\n\r\n";

        fwrite($fd, $out);

        // 101 switching protocols, see if echoes key
        $result= fread($fd,10000);

        preg_match('#Sec-WebSocket-Accept:\s(.*)$#mU', $result, $matches);

        $keyAccept = trim($matches[1]);
        $expectedResonse = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
        $handshaked = ($keyAccept === $expectedResonse) ? true : false;

        if ($handshaked){
            fwrite($fd, $this->hybi10Encode('42["' . $action . '", "' . addslashes($data) . '"]'));
            fread($fd,1000000);
            return true;
        } else {return false;}
    }
    private function generateKey($length = 16)
    {
        $c = 0;
        $tmp = '';
        while ($c++ * 16 < $length) { $tmp .= md5(mt_rand(), true); }
        return base64_encode(substr($tmp, 0, $length));
    }
    private function hybi10Encode($payload, $type = 'text', $masked = true)
    {
        $frameHead = array();
        $payloadLength = strlen($payload);
        switch ($type) {
            case 'text':
                $frameHead[0] = 129;
                break;
            case 'close':
                $frameHead[0] = 136;
                break;
            case 'ping':
                $frameHead[0] = 137;
                break;
            case 'pong':
                $frameHead[0] = 138;
                break;
        }
        if ($payloadLength > 65535) {
            $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8);
            $frameHead[1] = ($masked === true) ? 255 : 127;
            for ($i = 0; $i < 8; $i++) {
                $frameHead[$i + 2] = bindec($payloadLengthBin[$i]);
            }
            if ($frameHead[2] > 127) {
                $this->close(1004);
                return false;
            }
        } elseif ($payloadLength > 125) {
            $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8);
            $frameHead[1] = ($masked === true) ? 254 : 126;
            $frameHead[2] = bindec($payloadLengthBin[0]);
            $frameHead[3] = bindec($payloadLengthBin[1]);
        } else {
            $frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength;
        }
        foreach (array_keys($frameHead) as $i) {
            $frameHead[$i] = chr($frameHead[$i]);
        }
        if ($masked === true) {
            $mask = array();
            for ($i = 0; $i < 4; $i++) {
                $mask[$i] = chr(rand(0, 255));
            }
            $frameHead = array_merge($frameHead, $mask);
        }
        $frame = implode('', $frameHead);
        for ($i = 0; $i < $payloadLength; $i++) {
            $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
        }
        return $frame;
    }
}

Thank you for help!

解决方案

I was having the same problem with all the libraries that exists on github, the problem is that they are abandoned or not updated to socket.io V3.

In socket.io documentation says:

TL;DR: due to several breaking changes, a v2 client will not be able to connect to a v3 server (and vice versa)

To solve this problem, you need to learn how socket.io client works, this is easy because is in the protocol documentation, in the sample-session section.

Socket.Io protocol documentation

To solve this, you will need to forget the fsockopen and fwrite functions, you need to use CURL directly doing the requests mentioned in the protocol documentation.

Request n°1
GET
url: /socket.io/?EIO=4&transport=polling&t=N8hyd7H
Open packet: Open the connection between php and socket.io server. The server will return a "session id" named "sid", you will be adding this to the url query for the subsecuent queries.

Request n°2
POST
url: /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1
post body: '40'
Namespace connection request: You need to send in the body the number 40, as a string, this means that you want to connect to socket.io "message" type

Request n°3
GET
url: /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1
Namespace connection approval : This will return if the connection is successful or if there is an error, here is when the socket.io server authorizes your connection if you need a token.

Request n°4
POST
url: /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1
post body: 42[event,data]
For example 42["notifications","Hi, Im a notification"] and is equivalent to socket.emit(event,data) Emit message to server: Send your message to the socket.io server.

Here is a BASIC example using Symfony 5.2 and HttpClientInterface:

public function sendToSocket(HttpClientInterface $client)
{
    $first = $client->request('GET', 'http://localhost:3000/socket.io/?EIO=4&transport=polling&t=N8hyd6w');
    $res = ltrim($first->getContent(), '0');
    $res = json_decode($res, true);
    $sid = $res['sid'];

    $second = $client->request('POST', 'http://localhost:3000/socket.io/?EIO=4&transport=polling&sid='.$sid, [
            'body' => '40'
        ]);
    $third = $client->request('GET', 'http://localhost:3000/socket.io/?EIO=4&transport=polling&sid='.$sid);

    $fourth = $client->request('POST', 'http://localhost:3000/socket.io/?EIO=4&transport=polling&sid='.$sid, [
        'body' => '42["notifications","Hi, Im a notification"]'
    ]);

}

As you can see, is very easy, and you dont need the troubling "copy-pasted" libraries out there. I said "copy-pasted" because all use the same code to open de socket and send the information, but no one is compatible with socket.io V3.

Here is an image, proving that the given code works as January 4 2021 with php 7.4, symfony 5.2 and socket.io V3.

This is my test server in node

const app = require('express')();
const http = require('http').createServer(app);

const io = require('socket.io')(http, {
   cors: {
      origin: "*",
      methods: ["GET", "POST"]
   }
});

io.on('connection', function (socket) {

   console.log("New Connection with transport", socket.conn.transport.name);

   socket.on('notifications', function (data) {
      console.log(data);
   });
});

http.listen(3000, () => {
   console.log('Server started port 3000');
});

I need to say that this solution works excellent if you want to send "one direction" messages to your socket.io server, like a new notification or whatever that doesn't need a permanent connection, is just "one shot" and nothing else.

Happy coding and greetings from Mexico.

Here is another example:

First column is Postman making a request to the php server, simulating a server side event, like a new question created. In the response are the dumps of the response body from the 4 requests that you need to make.

Second column is the socket.IO node server running on port 3000

And the last column is the chrome console, simulating a user connected to the socket.IO server via websocket looking for notifications in 'questions' event.

这篇关于Socket.io 3 和 PHP 集成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆