Использование реактивного PHP в блокирующем приложении

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

Чтобы общаться с этой службой веб-сокетов, мы используем Ratchet — библиотеку PHP, основанную на реакции PHP.

Этот фрагмент кода должен отправить и ответить на пару запросов, а после этого должен вернуть информацию в «основной поток».

Пример потока:

HTTP-запрос -> контроллер -> Запускает службу, которая открывает клиент веб-сокета -> клиент веб-сокета разговаривает с сервером -> после его завершения он должен вернуть результат коду контроллера -> вывод контроллера пользователю

У меня проблема в том, что я не знаком с реактивным PHP и не знаю, как с этим справиться.

Я пытался;

    $service = new WebsocketService();
    $startTimer = time();
    $service->getList(44);
    while($service->getResponse() == null) {
        usleep(500);
        if (time() > $startTimer + 10) {
            continue; //Timeout on 10 seconds
        }
    }
    var_dump($service->getResponse());

Код службы установит для своей переменной «ответ» значение, отличное от нуля, как только это будет сделано. Это явно не работает, потому что метод сна блокирует поток. Кроме того, кажется, что цикл while блокирует ввод-вывод, и реактивный код дает сбой.

Решением было бы открыть новый поток и запустить там код веб-сокета, но я бы не был доволен этим.

Я чувствую, что мне нужно реализовать своего рода «наблюдатель» за процессом веб-сокета, но я не уверен, как это сделать.

Код нашего клиента службы Websocket выглядит следующим образом:

private $response = null;

/**
 * @return null|object
 */
public function getResponse() {
    return $this->response;
}

public function getList($accountId) {
    $this->response = null;
    \Ratchet\Client\connect('ws://192.168.56.1:8080')->then(function(\Ratchet\Client\WebSocket $conn) use ($accountId) {
        $login = new \stdClass();
        $login->action = 'login';
        $conn->on('message', function($msg) use ($conn, $login, $accountId) {
            try {
                $response = json_decode($msg);
                if ($response->result_id == 100) {
                    //Succesfully logged in to websocket server

                    //Do our request now.
                    $message = new \stdClass();
                    $message->target = 'test';
                    $conn->send(json_encode($message));
                }

                if (isset($response->reply) && $response->reply == 'list') {
                    $this->response = $response; //This is the content I need returned in the controller
                    $conn->close(); //Dont need it anymore
                }

            } catch (\Exception $e) {
                echo 'response exception!';
                //Do nothing for now
            }
        });

        $conn->send(json_encode($login));
    }, function ($e) {
        echo "Could not connect: {$e->getMessage()}\n";
    });
}

Запуск такого кода также не работает;

    $service = new WebsocketService();
    $service->getList(44);
    echo 'Test';
    var_dump($service->getResponse());

потому что «тестовое» эхо приходит еще до того, как я получаю ответ от сервера веб-сокетов.

Пожалуйста, просветите меня! Я не уверен, что искать.


person Rob    schedule 30.03.2017    source источник


Ответы (2)


PHP и веб-сокеты все еще кажутся немного экспериментальными. Тем не менее, я нашел на medium.com отличный учебник, написанный Адамом Виннипассом, который должен быть действительно полезен для решения вашей проблемы: https://medium.com/@winni4eva/php-websockets-with-ratchet-5e76bacd7548

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

Установка подключения через веб-сокет

Похоже, что одна из возможностей создать успешное соединение через веб-сокет — это расширить MessageComponentInterface.

use Ratchet\MessageComponentInterface;

что также требует

use Ratchet\ConnectionInterface;

Интерфейс компонента сообщения определяет следующие методы:

  • onOpen
  • onMessage
  • при закрытии
  • при ошибке

И я думаю, что именно так это реализовано в библиотеке Ratchet. Вот как они, наконец, запускают свой сервер:

use Ratchet\Server\IoServer;
use MyApp\MyCustomMessageComponentInterface;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require dirname(__DIR__) . '/vendor/autoload.php';
$server = IoServer::factory(
            new HttpServer(
                new WsServer(
                    new MyCustomMessageComponentInterface()
                )
            ),
          8080
         );
$server->run();

С этой архитектурой вы уже можете получать (onMessage), а отправка также возможна с помощью метода send().

Я не могу решить точную проблему с вашим существующим кодом. Но я предполагаю, что если вы используете предварительно созданные классы и интерфейсы библиотеки по назначению (и продемонстрировано здесь), вы сможете добиться того, чего хотите, добавив свой код в соответствующие методы.

Дополнительную информацию и примеры можно найти в документации:

http://socketo.me/docs/server

http://socketo.me/api/namespace-Ratchet.html

person Blackbam    schedule 25.06.2019

Вы расширяете класс с помощью WsServer. Это может быть проблемой, если вы получаете фатальные ошибки. Я не уверен, получаете ли вы фатальные ошибки или предупреждения. Также я заметил, что публичная функция onOpen() открывает соединение. Попробуйте сослаться на этот документ http://socketo.me/api/class-Ratchet.WebSocket.WsServer.html может оказаться полезным.

person Atul Jindal    schedule 31.03.2017
comment
Нет, сервер веб-сокетов написан на Node.JS, и мне просто нужен клиент для общения с ним. Посмотрите на это как на простой file_get_contents(), но только через соединение Websocket и большее рукопожатие. - person Rob; 31.03.2017