PHP/Ratchet WebSocket -while循环的问题

0pizxfdo  于 2023-02-03  发布在  PHP
关注(0)|答案(3)|浏览(201)

我有一个非常简单的WebSocket使用PHP和棘轮库。
当一个用户打开一个特定的页面时,它会将用户ID发送到我的套接字,并且它应该会更新该用户的状态(目前我只是在控制台中记录它),如下所示:

<input type="hidden" value="'.$account_id.'" id="account_id">
<input type="hidden" value="trial" id="request_type">
<script>
$(document).ready(function(){
    var conn = new WebSocket('ws://127.0.0.1:8080');

    conn.onopen = function(e){
        console.log("Connection Opened!");
        var account_id = $("#account_id").val();
        var request_type = $("#request_type").val();
        var data = {account_id: account_id, request_type: request_type};
        conn.send(JSON.stringify(data));
    }
    conn.onclose = function(e){
        console.log("Connection Closed!");
    }
    conn.onmessage = function(e) {
        var data = JSON.parse(e.data);
        console.log(data);
    };
    conn.onerror = function(e){
        var data = JSON.parse(e.data);
        console.log(data);
    }
})
</script>

那么我的套接字脚本如下所示:

set_time_limit(0);

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require dirname(__DIR__) . '../vendor/autoload.php';

class socket implements MessageComponentInterface{
    protected $clients;

    public function __construct(){
        $this->clients = new \SplObjectStorage;
        echo 'Server Started.'.PHP_EOL;
    }

    public function onOpen(ConnectionInterface $socket){
        $this->clients->attach($socket);
        echo 'New connection '.$socket->resourceId.'!'.PHP_EOL;
    }
    public function onClose(ConnectionInterface $socket) {
        $this->clients->detach($socket);
        echo 'Connection '.$socket->resourceId.' has disconnected'.PHP_EOL;
    }
    public function onError(ConnectionInterface $socket, \Exception $e) {
        echo 'An error has occurred: '.$e->getMessage().'!'.PHP_EOL;
        $socket->close();
    }
    public function onMessage(ConnectionInterface $from, $json){
        echo 'Connection '.$from->resourceId.' sent '.$json.PHP_EOL;
        $data = json_decode($json, true);
        $account_id = $data['account_id'];
        $request_type = $data['request_type'];

        try {
            $conn = new PDO("mysql:host=".$db_host.";port:".$db_port.";dbname=".$db_name."", $db_user, $db_pass);
            $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }catch(PDOException $e){
            echo $e->getMessage();
        }
        
        foreach ($this->clients as $client) {
            if ($from->resourceId == $client->resourceId) {
                if($request_type == 'trial'){
                    // while(true){
                        $response_array= [];
                        $stmt = $conn->prepare("SELECT * FROM table WHERE account_id=:account_id AND last_status_change=now()");
                        $stmt->bindParam(':account_id', $account_id);
                        $stmt->execute();
                        $result = $stmt->setFetchMode(PDO::FETCH_ASSOC);
                        foreach($stmt->fetchAll() as $key=>$value) {
                            $response_array[$key] = $value;
                        }
                        if(!empty($response_array)){
                            foreach($response_array as $item){
                                $status = $item['status'];
                            }
                            $response = array(
                                'account_id' => $account_id,
                                'status' => $status
                            );
                            var_dump($response);
                            $client->send(json_encode($response));
                        }
                        // sleep(5);
                    // }
                }
            }
        }
    }
}

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new socket()
        )
    ),
    8080
);
$server->run();

目前,它按预期工作,但只有在页面加载时状态发生变化时才给出当前状态,我将在控制台中看到状态,只要我取消注解while()循环以实际检查更新状态,我的套接字将执行命令行中的var_dump()结果,当状态发生变化,但没有在客户端中记录任何内容时。
我刚接触网络插座,我一直在做长时间的轮询,在JS中有一个间隔,发送一个fetch()到一个PHP脚本,获得最新的DB结果,但它不是很有效,当大量的客户端处于活动状态并不断地向文件发出请求时会导致问题,这反过来又会减慢DB。我不确定为什么while()循环会这样影响它,或者我是否以正确的方式处理它。

r7s23pms

r7s23pms1#

您应该使用Ratchet的addPeriodicTimer,尽管您必须将$clients设置为public才能放置计时器。也许您可以将其放置在类中,但仍然是private,但我不确定它是否可以为每个客户端启动计时器。
无论如何,正如您所看到的,您可以创建另一个公共函数,它将在周期性计时器中实际执行该工作(就像while循环一样),然后在客户端连接后调用它,并在timerloop中多次调用它,为此,我还创建了一个公共account_ids来跟踪帐户ID
给予看然后告诉我

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require dirname(__DIR__) . '../vendor/autoload.php';

class socket implements MessageComponentInterface{
    public $clients;
    public $account_ids;

    public function __construct(){
        $this->clients = new \SplObjectStorage;
        echo 'Server Started.'.PHP_EOL;
    }

    public function onOpen(ConnectionInterface $socket){
        $this->clients->attach($socket);
        echo 'New connection '.$socket->resourceId.'!'.PHP_EOL;
    }
    public function onClose(ConnectionInterface $socket) {
        $this->clients->detach($socket);
        echo 'Connection '.$socket->resourceId.' has disconnected'.PHP_EOL;
    }
    public function onError(ConnectionInterface $socket, \Exception $e) {
        echo 'An error has occurred: '.$e->getMessage().'!'.PHP_EOL;
        $socket->close();
    }
    public function onMessage(ConnectionInterface $from, $json){
        echo 'Connection '.$from->resourceId.' sent '.$json.PHP_EOL;
        $data = json_decode($json, true);
        $account_id = $data['account_id'];
        $request_type = $data['request_type'];
        foreach ( $this->clients as $client ) {
            if ( $from->resourceId == $client->resourceId ) {
                if( $request_type == 'trial'){
                    $this->account_ids[$client->resourceId] = $account_id;
                    $this->checkStatus($client, $account_id);
                }
            }
        }
    }
    public function checkStatus($client, $account_id){
        try {
            $conn = new PDO("mysql:host=".$db_host.";port:".$db_port.";dbname=".$db_name."", $db_user, $db_pass);
            $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }catch(PDOException $e){
            echo $e->getMessage();
        }
        $response_array= [];
        $stmt = $conn->prepare("SELECT * FROM table WHERE account_id=:account_id AND last_status_change=now()");
        $stmt->bindParam(':account_id', $account_id);
        $stmt->execute();
        $result = $stmt->setFetchMode(PDO::FETCH_ASSOC);
        foreach($stmt->fetchAll() as $key=>$value) {
            $response_array[$key] = $value;
        }
        if ( !empty($response_array) ) {
            foreach($response_array as $item){
                $status = $item['status'];
            }
            $response = array(
                'account_id' => $account_id,
                'status' => $status
            );
            var_dump($response);
            $client->send(json_encode($response));
        }
    }
}

$socket = new socket();
$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            $socket
        )
    ),
    8080
);
$server->loop->addPeriodicTimer(5, function () use ($socket) {
    foreach($socket->clients as $client) {
        echo "Connection ".$client->resourceId." check\n";
        $socket->checkStatus($client, $socket->account_ids[$client->resourceId]);
    }
});

$server->run();
aor9mmx1

aor9mmx12#

while循环不是这样工作的,它会阻塞东西,无限地、不必要地消耗资源。
你想要的是addPeriodicTimer()
定期检查需要更新的客户端。
在你的引导中加入这样的内容:

$reactEventLoop->addPeriodicTimer(5, function() use $messageHandler, $server {
    // Fetch all changed clients at once and update their status
    $clientsToUpdate = getUpdatedClients($server->app->clients);
    foreach ($clientsToUpdate as $client) {
        $client->send(json_encode($response));
    }
});

这比任何其他方法都要轻量级得多,因为您可以
1.使用单个准备好的数据库查询获取N个客户端状态
1.定期仅更新更改的客户端
1.不将应用置于阻止状态
Stackoverflow上的其他资源将帮助您找到正确的位置:
How do I access the ratchet php periodic loop and client sending inside app?
Periodically sending messages to clients in Ratchet

5cg8jx4n

5cg8jx4n3#

if ($from->resourceId == $client->resourceId) {替换为if ($from == $client) {这个修改看起来很简单,但是在php ratchet提供的示例Chat类中,为了避免将消息发送给发送者,它们有一个条件将消息发送给除发送者之外的客户端,它们比较如下if ($from == $client) {不仅是resourceId整个对象本身!

相关问题