hoaproject / Websocket

The Hoa\Websocket library.
https://hoa-project.net/
423 stars 75 forks source link

Sending messages from server to clients #69

Open Shkarbatov opened 8 years ago

Shkarbatov commented 8 years ago

Hi, I have some problems, unfortunately I can't find this in documentation, so hope you will help me.

I have own API, that receive message and this message I need to show.

Incoming message from API, I saved in DB on one day.

Message can be for everyone users, for some group or just for one.

I send user data from javascript to the socket once, when a page is rendered.

socket.send(JSON.stringify({'user_id': 'cf3wfdf3', 'user_nickname': 'nickname'}));

On server side I need to add my business logic. I trying like this:

    $web_socket->on('message', function (Bucket $bucket) {
        $data = $bucket->getData();

        while (true) {
            if ($this->personalMessage()) {
                $bucket->getSource()->send('Personal message');

            } else if ($this->groupMessage()) {
                $bucket->getSource()->broadcastIf(function() {}, 'For all');

            } else if ($this->broadcastMessage()) {
                $bucket->getSource()->broadcast('For all');
            }

            sleep(3);
        }
        return;
    });

But while (true) {} will blocking SocketServer, and it will be works only for first client.

And trying like this:

    $web_socket = new WebsocketServer(
        new SocketServer('ws://127.0.0.1:8889')
    );
    $web_socket->run();

    while (true) {
        sleep(1);
        $web_socket->broadcast('For all');
    }

But it not works too =(

Questions: 1) How I can send message from server to client, not only when client send message? 2) How I can identify user as node, to filter it in future and how to filter it by user_id? 2) Where I need to adding while(true) {} ?

Thanks for any advice!

Hywan commented 8 years ago

Hello :-),

So let's start by question 2 (the first one, because you have two 2 😉). Each connection is represented by a node. Maybe this Section of the documentation might help you, http://hoa-project.net/En/Literature/Hack/Websocket.html#Customized_node.

Next, question 1. PHP is synchronous by nature. You cannot listen to incoming message while having another execution where you send messages. This is possible if you use pthread for instance or some tricks to get a pseudo-asynchronous execution with generators and co-routines.

Finally, question 3. Answered with the previous paragraph.

Shkarbatov commented 8 years ago

1) I think you may have misunderstood me, will try to explaine.

2) Yes, I think it will be help, need to try, thanks.

3) I understand, but how you can solved this problem, if you need answer each client every minute? Run two process, one for read and one for send or will sending message from client each minute, or some another way?

Hywan commented 8 years ago

So you want that:

  1. the server replies to the client when a new message is coming,
  2. but also sending spontaneously messages to every client each x ticks.

Is it correct?

Shkarbatov commented 8 years ago

Yes and yes.

I think, I can extend the loop circul, and call once in 3 seckonds my method that sending data to clients.

Whay You think abut this?

Hywan commented 8 years ago

You cannot have 2 infinite loops inside the same thread. When calling the famous run method, an infinite loop is starting. If you are starting another infinite loop inside a listener, this will not work.

I recommend 2 solutions: Using pthread, or using generators/co-routines libraries to simulate asynchronous execution. Most people uses pthread for this usages.

Or, you can use 2 servers.

Hywan commented 8 years ago

Did you have your answer or should we keep this issue open?

Shkarbatov commented 6 years ago

Can we create something like this: https://github.com/Shkarbatov/WebSocketPHPWorkerman/blob/master/worker.php ?

Hywan commented 6 years ago

This is almost the actual API of Hoa\Websocket\Server or Hoa\Websocket\Client. You add listeners, and you run the instance.

What do you mean?

Shkarbatov commented 6 years ago

Can I send data from server to client with Hoa like I do this with workerman? Not sending every second request from web through WebSockets to server.

Pierozi commented 6 years ago

Hello @Shkarbatov, Yes, as long you save the client (fro source bucket) during client established connection to the server.

Shkarbatov commented 6 years ago

Thanks for answer, do you have any example?

Shkarbatov commented 6 years ago

Trying next:

Worker:

use Hoa\Websocket\Server as WebsocketServer;
use Hoa\Socket\Server as SocketServer;
use Hoa\Event\Bucket;

$subscribedTopics = array();

// =================================

$websocket_php = new WebsocketServer(
    new SocketServer('ws://127.0.0.1:8009')
);

$websocket_php->on('open', function (Bucket $bucket) { });

$websocket_php->on('message', function (Bucket $bucket) use (&$subscribedTopics) {
    $data = json_decode($bucket->getData()['message'], true);

    if (isset($data['user']) and isset($subscribedTopics[$data['user']])) {
        $subscribedTopics[$data['user']]->getSource()->send($data['command']);
    }
});

// =================================

$websocket_web = new WebsocketServer(
    new SocketServer('ws://127.0.0.1:8008')
);

$websocket_web->on('open', function (Bucket $bucket) use (&$subscribedTopics) {
    $subscribedTopics[substr($bucket->getSource()->getRequest()->getUrl(), 7)] = $bucket;
});

$websocket_web->on('message', function (Bucket $bucket) {
    $bucket->getSource()->send('Socket connected');
});

$websocket_web->on('close', function (Bucket $bucket) { });

// =================================

$group     = new Hoa\Socket\Connection\Group();
$group[]   = $websocket_web;
$group[]   = $websocket_php;

$group->run();

Web Client:

<script>
    var ws = new WebSocket('ws://site.ll:8008/?user=tester01');
    ws.onmessage = function(evt) { console.log(evt); };
    ws.onopen = function() {
        console.log("connect");
        ws.send("hello");
    };
    ws.onerror = function(error) {
        console.log("Error " + error.message);
    };
</script>

PHP Client:

$client = new Hoa\Websocket\Client(new Hoa\Socket\Client('tcp://127.0.0.1:8009'));
$client->setHost('127.0.0.1');
$client->connect();
$client->send(json_encode(['user' => 'tester01', 'command' => '111111']));
$client->close();

Web client connected to worker and save his backet, then I run php client and worker receive message, find backet needed web client and send message, but it not send and I don't have any errors.

Any idea what it can be?

Shkarbatov commented 6 years ago

Any idea, why it is not working?

Shkarbatov commented 6 years ago

Well, problem next:

I saved bucket next:

$subscribedTopics[substr($bucket->getSource()->getRequest()->getUrl(), 7)] = $bucket;

And then, when I want to send:

$subscribedTopics[$data['user']]->getSource()->send($data['command']);

if I will not put Socket\Node as the second parametr of the send function it will used current node:

if (null === $node) {
    $node = $this->getConnection()->getCurrentNode();
}

This is ok, but if I will used Node from saved backed:

$subscribedTopics[$data['user']]->getSource()->send($data['command'], 
$subscribedTopics[$data['user']]->getSource()->getConnection()->getCurrentNode());

It will return current node, not saved node.

So I need to save separate bucket and node:

$subscribedTopics[substr($bucket->getSource()->getRequest()->getUrl(), 7)] =
    ['bucket' => $bucket, 'node' => $bucket->getSource()->getConnection()->getCurrentNode()];

And when send message add saved node:

$subscribedTopics[$data['user']]['bucket']->getSource()->send(
    $data['command'],
    $subscribedTopics[$data['user']]['node']
);

Is this bug? Can I somehow receive node from saved bucked? I can receive node id, but there no method receive node by id, only all with method getNodes()