简体   繁体   English

Socket.io 3和PHP整合

[英]Socket.io 3 and PHP integration

I using PHP SocketIO class to connect NodeJS application and send messages.我使用 PHP SocketIO class 连接 NodeJS 应用程序并发送消息。 Everything worked wonderfully with Socket.io 2 but after upgrade to version 3 the PHP integration is stopped working.一切都与 Socket.io 2 完美配合,但在升级到版本 3 后,PHP 集成停止工作。

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.但是我在 NodeJS 端看不到任何东西,即使我尝试使用“连接”事件记录与服务器的任何连接也是如此。

This is the PHP class:这是 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.我对 github 上存在的所有库都遇到了同样的问题,问题是它们被放弃或没有更新到 socket.io V3。

In socket.io documentation says:在 socket.io 文档中说:

TL;DR: due to several breaking changes, a v2 client will not be able to connect to a v3 server (and vice versa) TL;DR:由于几个重大更改,v2 客户端将无法连接到 v3 服务器(反之亦然)

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 客户端的工作原理,这很容易,因为在协议文档的示例会话部分中。

Socket.Io protocol documentation Socket.Io协议文档

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.要解决这个问题,您需要忘记 fsockopen 和 fwrite 函数,您需要直接使用 CURL 执行协议文档中提到的请求。

Request n°1请求 n°1
GET得到
url : /socket.io/?EIO=4&transport=polling&t=N8hyd7H url :/socket.io/?EIO=4&transport=polling&t=N8hyd7H
Open packet: Open the connection between php and socket.io server.打开数据包:打开php和socket.io服务器之间的连接。 The server will return a "session id" named "sid", you will be adding this to the url query for the subsecuent queries.服务器将返回一个名为“sid”的“会话 id”,您将把它添加到 url 查询中以进行后续查询。

Request n°2请求 n°2
POST邮政
url : /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1 url :/socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1
post body : '40'帖子正文:'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命名空间连接请求:您需要在正文中发送数字 40,作为字符串,这意味着您要连接到 socket.io “消息”类型

Request n°3请求 n°3
GET得到
url : /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1 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.命名空间连接批准:如果连接成功或出现错误,这将返回,这是 socket.io 服务器授权您的连接时,如果您需要令牌。

Request n°4请求 n°4
POST邮政
url : /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1 url :/socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1
post body : 42[event,data]帖子正文:42[事件,数据]
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.例如 42["notifications","Hi, Im a notification"] 相当于 socket.emit(event,data)向服务器发送消息:将您的消息发送到 socket.io 服务器。

Here is a BASIC example using Symfony 5.2 and HttpClientInterface:这是一个使用 Symfony 5.2 和 HttpClientInterface 的 BASIC 示例:

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.我说“复制粘贴”是因为都使用相同的代码打开 de socket 并发送信息,但没有一个与 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.这是一张图片,证明给定的代码在 2021 年 1 月 4 日使用 php 7.4、symfony 5.2 和 socket.io V3。

Socket.io v3 php

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.我需要说的是,如果您想向 socket.io 服务器发送“单向”消息(例如新通知或不需要永久连接的任何内容),则此解决方案非常有效,只是“一击”而已。

Happy coding and greetings from Mexico.来自墨西哥的快乐编码和问候。

Here is another example:这是另一个例子: Socket.io v3 php

First column is Postman making a request to the php server, simulating a server side event, like a new question created.第一列是 Postman 向 php 服务器发出请求,模拟服务器端事件,就像创建一个新问题一样。 In the response are the dumps of the response body from the 4 requests that you need to make.在响应中是您需要发出的 4 个请求的响应正文的转储。

Second column is the socket.IO node server running on port 3000第二列是socket.IO节点服务器运行在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.最后一列是 chrome 控制台,模拟连接到 socket.IO 服务器的用户,通过 websocket 在“问题”事件中寻找通知。

Try robgonnella/socket.io-php-emitter, per https://github.com/rase-/socket.io-php-emitter/issues/36根据https://github.com/rase-/socket.io-php-emitter/issues/36尝试 robgonnella/socket.io-php-emitter

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM