Клиент PHP Websocket — Держите соединение открытым

#php #websocket

Вопрос:

Я использую PHP-WSS в приложении laravel, и мне нужно, чтобы клиент websocket был открыт для получения различных сообщений с сервера websocket.

До сих пор я создавал php-скрипт CLI, который я могу выполнять и ждать поступления сообщений.

Я построил следующую функцию для тестирования…

Вопрос в том, чтобы сохранить соединение открытым для любых сообщений, которые могут быть отправлены с сервера, является ли хорошим подходом сделать это, как показано ниже, используя цикл while(true)? Есть ли в любом случае, что я могу сделать это лучше? (Для меня это выглядит грязно, и я хочу улучшить его и сделать это правильно)

 function testWebsocketClient() {
    $url = 'wss://example.com/?token=xyz123456';
    $client = new WebSocketClient($url, new ClientConfig());
    while(true){
        sleep(5);
        $client->send('test');
        $return = $client->receive(); // test received OK
    }
    return $return;
}
 

ОБНОВЛЕНИЕ: Любой, кто использует PHP-WSS, я обнаружил ошибку в Connection.php в широковещательном методе.

Исходная функция пытается отправить на мертвое соединение, которое показывает следующую ошибку Пустое чтение; соединение мертво? (Обратите внимание, что EOF = true)

 public function broadCast(string $data): void
{
    foreach ($this->clients as $client) {
        if (is_resource($client) ) { // check if not yet closed/broken etc
            fwrite($client, $this->encode($data));
        } else {
            echo 'Skipping a closed connection';
        }
    }
}
 

Я изменил его на

 public function broadCast(string $data): void
{
    foreach ($this->clients as $client) {
        //echo PHP_EOL. stream_get_status($client) .PHP_EOL;
        $clientMeta = ( stream_get_meta_data($client) );
        $clientEof = $clientMeta['eof'];
        if (is_resource($client) amp;amp; $clientEof == false ) { // check if not yet closed/broken etc
            fwrite($client, $this->encode($data));
        } else {
            echo 'Skipping a closed connection';
        }
    }
}
 

Комментарии:

1. кто закрывает вашу связь?

2. Предполагается, что Websocket обеспечивает постоянное соединение. Зачем вам здесь использовать длительный опрос?

3. Как вы можете видеть, это PHP-скрипт, если я опущу бесконечный цикл, скрипт завершится до получения возможного ответа. Не уверен, что я чего-то здесь не понимаю.

Ответ №1:

Поскольку php-скрипт, отображающий страницу, завершает выполнение, вам необходимо реализовать websockets на самом клиенте с помощью js-скрипта

 <?php 
   // here output your page
   wss_path = 'wss://example.com/?token=xyz123456';
?>

// JS script (see its working results in console by pressing `F12`)
<script>
  let socket = new WebSocket(<?= wss_path ?>);

  socket.onopen = function(e) {
    console.log('[open] Connection opened');
    socket.send("Hi! I am a new web-socket client.");
  };

  socket.onmessage = function(event) {
    const message = JSON.parse(event.data);
    console.log('From the server:', message);
  };

  socket.onclose = function(event) {
    if (event.wasClean) {
      console.log(`[close] Connetion closed clearly, code=${event.code} reason=${event.reason}`);
    } else {
      console.log('[close] Сonnection closed unexpectedly');
    }
  };

  socket.onerror = function(error) {
    console.log(`[error] ${error}`);
  };
</script>
 

Комментарии:

1. Спасибо за ваш ответ, решение JS-это то, что я находил по всему Интернету, я пытаюсь сделать то же самое, но с помощью PHP CLI мне нужно, чтобы это всегда работало в фоновом режиме на одном из моих серверов. Вот почему в моем первоначальном вопросе указан PHP. Или, может быть, возможно, рассматривал возможность использования JS с использованием узла. Не уверен 🙁

2. Я понимаю. Так что, если это без интерфейса, с которым ваше решение while ближе, но я думаю, что вам не нужно ничего вставлять в тело, просто одно условие, чтобы прервать цикл, когда вы хотите закрыть свое приложение.

3. Мы высоко ценим ваш ответ. До сих пор я сделал хороший шаг вперед с тем временем(правда) … дело в том, что эта библиотека PHP-WSS выдает ошибку, когда читать нечего, поэтому я предполагаю, что либо мне все еще чего-то не хватает, либо мне нужно написать свой собственный поток чтения. Любые дополнительные советы приветствуются, так как я чувствую, что зацикливаюсь быстрее, чем цикл while(true) в этом :S

4. Я подумаю об этом

5. отлично, я рад, что смог помочь

Ответ №2:

После целой недели борьбы с этим я нашел решение…

Существует обработчик ошибок, который выдает исключение, если буфер пуст, тем самым завершая работу клиента.

В WscMain.php файл, внутри защищенной функции метод чтения, удалите/закомментируйте (или измените по своему вкусу и потребностям) следующий обработчик ошибок (приблизительно строка 302)

 if(false){ // added a false condition so the code within doesn't execute
    if (false amp;amp; $buff === false ) {
        $metadata = stream_get_meta_data($this->socket);
        throw new ConnectionException(
            'Broken frame, read ' . strlen($data) . ' of stated '
            . $len . ' bytes.  Stream state: '
            . json_encode($metadata),
            CommonsContract::CLIENT_BROKEN_FRAME
        );
    }

    if (false amp;amp; $buff === '') {
        //print_r($this->socket);
        
        $metadata = stream_get_meta_data($this->socket);
        throw new ConnectionException(
            'Empty read; connection dead?  Stream state: ' . json_encode($metadata). ' '.PHP_EOL.( (int) $this->socket ) ,
            CommonsContract::CLIENT_EMPTY_READ
        );
    }
}
 

PS: В моем случае я также сделал очень небольшой (1 сек) тайм-аут чтения в конфигурации, как показано ниже. (Эта тестовая функция выполняется из PHP-скрипта CLI)

 function runWebsocketClientTest(){

    echo PHP_EOL;
    echo __FUNCTION__;
    echo PHP_EOL;
    $timeout = 1;
    $return = '';
    $config = new ClientConfig();
    $config->setTimeout($timeout);
    $client = new WebSocketClient('ws://localhost:8000', $config);

    while($client->isConnected()){
        if($client->isConnected()){ // this might be unnecessary 
            echo $client->receive();
        }
    }
    
}
 

Надеюсь, это кому-то поможет, и спасибо всем тем, кто помогал.