sirn-se / websocket-php

[php-lib] WebSocket client and server in PHP
Other
117 stars 18 forks source link

Help me understand Client - Server #74

Closed ETMTrader closed 1 month ago

ETMTrader commented 2 months ago

Hi I don't know much about websockets I have a question

I have created a websocket client and I get some data all the time.

How can I pass this data to the client web page?

I have seen the Client and Server solution but I don't understand how to do it.

I have a simple code

function ws_connect()
{
    $client = new WebSocket\Client(WS_SERVER);

    $client
        ->setTimeout(60)
        ->setFrameSize(1024)
        ->setPersistent(true)
        ->setContext([
            "ssl" => [
                "verify_peer" => false,
                "verify_peer_name" => false,
            ],
        ])
        ->addHeader("Sec-WebSocket-Protocol", "soap")
        ->addHeader("Origin", "localhost");

    return $client;
}
function ws_work($client)
{
    // Get current settings
    echo "timeout:      {$client->getTimeout()}s\n";
    echo "frame size:   {$client->getFrameSize()}b\n";
    echo "running:      {$client->isRunning()}\n";

    $client->onConnect(function ($client, $connection) {
        //ws_login($client);
    })
    ->onText(function ($client, $connection, $message) {
        ws_parser($message->getContent());
    })
    ->onClose(function ($client, $connection, $message) {

    })
    ->onError(function ($client, $connection, $exception) {
        $client = ws_connect();
        //ws_login($client);
        ws_work($client);
    })->start();
}

$client = ws_connect();
ws_work($client);
sirn-se commented 2 months ago

It depends on what you want to achieve.

If the code above is included on a php webpage, and you only want to output one or a few messages, you could just output it as you would with any php code.

But if you want to continuously update the page without reloading it, you probably want a javascript based client instead.

ETMTrader commented 2 months ago

Thank you for reply.

I am getting currency prices and other data.

Right now I am running “php client.php start” in terminal and I am getting a lot of data every second.

So now i need to make a server? 1 - to be able to connect from another server and get data too 2 - to be able to get data in browser using javascript websocket

ev-gor commented 2 months ago

Where are you getting your quotes from? If it's an existing web-socket server, then you just need to use WebSocket JavaScript object to update the quotes on the web page in real time. Otherwise, you should build your own web-socket server first to broadcast quotes and connect to it from js script.

ETMTrader commented 2 months ago

Yes, I'm connecting to an existing server. But I can only connect my websocket there.

I need to get data from one server and distribute this data to many clients.

client-server

ev-gor commented 2 months ago

Then you need both a websocket server and a websocket client. Your clients will connect to your server, and your websocket client will connect to a 3d party API server and every time it gets a new quote, this quote can be sent to all connected clients.

ETMTrader commented 2 months ago

Thank for reply.

I don't understand how I can create a server and send onText messages there. I have a client.php file where I get the messages. How do I create a server and connect it all? Maybe there is an example somewhere?

I apologize if the questions are stupid)

ev-gor commented 2 months ago

OK. There is a VERY simple implementation for your case. Of cause, it doesn't fit for real production, just to give you a clue. client.php:


$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$client = new WebSocket\Client("wss://<3d-party-api-ws-server>");

$client
    // Add standard middlewares
    ->addMiddleware(new WebSocket\Middleware\CloseHandler())
    ->addMiddleware(new WebSocket\Middleware\PingResponder())
    // Listen to incoming Text messages
    ->onText(function (WebSocket\Client $client, WebSocket\Connection $connection, WebSocket\Message\Message $message) use ($redis) {
        $redis->publish('test_channel', $message->getContent());
    })
    ->start();

You get prices from your provider and send them to Redis, for example. Of cause, you can use other solutions: write to databases, files, queues, messages brokers of your choice. Now server.php


<?php

use Swoole\Coroutine as co;
use Swoole\WebSocket\Server;

//create async ws server
$server = new Server("127.0.0.1", 5000);

// When server starts
$server->on("Start", function ($server) {
    // Start a new coroutine to subscribe to Redis channel
    go(function () use ($server) {
        $redis = new co\Redis();
        $redis->connect('127.0.0.1', 6379);
        $msg = $redis->subscribe(['test_channel']);
        while ($msg = $redis->recv()) {
            // Broadcast message to all clients
            foreach ($server->connections as $fd) {
                $server->push($fd, $msg[2]);
            }
        }
    });
});

// When a client connects
$server->on('open', function ($server, $req) {
    echo "Client {$req->fd} connected.\n";
});

// When a message is received
$server->on('message', function ($server, $frame) {
    echo "Received message from {$frame->fd}: {$frame->data}\n";
});

// When a client disconnects
$server->on('close', function ($server, $fd) {
    echo "Client {$fd} disconnected.\n";
});

// Start the server
echo 'Starting the server...' . PHP_EOL;
$server->start();

Here we use Swoole websocket server and redis client because they are async and better fit for your case. When we start the server, we subscribe to our Redis channel where our ws client sends quotes and on every quote we broadcast it to every connected frontend client. Simple example of web client:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Client</title>
</head>
<body>
    <h1>WebSocket Client</h1>
    <div id="messages"></div>
    <script>
        const socket = new WebSocket('ws://localhost:5000');

        socket.onmessage = (event) => {
            const messageDiv = document.getElementById('messages');
            messageDiv.innerHTML += `<p>${event.data}</p>`;
        };

        socket.onopen = () => {
            console.log('Connected to server');
        };

        socket.onclose = () => {
            console.log('Disconnected from server');
        };
    </script>
</body>
</html>

I hope now you have a general idea how it should work.

sirn-se commented 1 month ago

This library includes both a client and a server, so you don't really need to use additional extensions.

Below is a very simple solution running in a single file. Downside is that it need to switch between client and server listeners, so there will be a short delay, as specified by $timeout. It is tempting to set $timeout to 0, but don't - that will cause the script to use all available processing power even when doing nothing.

(You need to add some configuration, error handling, and initialization.)

<?php

require __DIR__ . '/vendor/autoload.php';

// Will switch context $timeout seconds or sooner
$timeout = 5;

$local_port = 80;
$local_ssl = false;
$remote_server_uri = "ws://remote_server";

// Setup server
$server = new WebSocket\Server($local_port, $local_ssl);
$server
    ->addMiddleware(new WebSocket\Middleware\CloseHandler())
    ->addMiddleware(new WebSocket\Middleware\PingResponder())
    ->setTimeout($timeout)
    ->onTick(function ($server) {
        // We need to stop the listener to give client a chance to run
        echo "Stop server \n";
        $server->stop();
    })
    ;

// Setup client
$client = new WebSocket\Client($remote_server_uri);
$client
    ->addMiddleware(new WebSocket\Middleware\CloseHandler())
    ->addMiddleware(new WebSocket\Middleware\PingResponder())
    ->setTimeout($timeout)
    ->onText(function ($client, $connection, $message) use ($server)  {
        // Broadcast received message to all connected clients
        echo " -- Delegating {$message->getContent()} \n";
        $server->send($message);
    })
    ->onTick(function ($client) {
        // We need to stop the listener to give server a chance to run
        echo "Stop client \n";
        $client->stop();
    })
    ;

// Run loop
while (true) {
    echo "Start server \n";
    $server->start();
    echo "Start client \n";
    $client->start();
}

As @ev-gor is suggesting, you can also run client and server in two separate scripts using a shared storage as intermediate. A bit more complex but no delay using that strategy.

A queue based intermediate such as Redis or RabbitMQ would be the best solution, but you can also use a database, Memcached, or similar. These require 3d party services or extensions, so if you're running on a pre-configured server it might limit your options.