Sending messages from PHP script to multiple Ratchet Websocket apps (via ZMQ Socket)
Asked Answered
M

1

7

So, I am running a Ratchet (php) websocket server with multiple routes that connect do multiple Ratchet apps (MessageComponentInterfaces):

//loop 
$loop   = \React\EventLoop\Factory::create();

//websocket app
$app = new Ratchet\App('ws://www.websocketserver.com', 8080, '0.0.0.0', $loop);

/*
 * load routes
 */
$routeOne = '/example/route';
$routeOneApp = new RouteOneApp();
$app->route($routeOne, $routeOneApp, array('*'));

$routeTwo = '/another/route';
$routeTwoApp = new AnotherApp();
$app->route($routeTwo, $routeTwoApp, array('*'));

From here I am binding a ZMQ socket, in order to be able to receive messages sent from php scripts run on the normal apache server.

// Listen for the web server to make a ZeroMQ push after an ajax request
$context = new \React\ZMQ\Context($loop);
$pull = $context->getSocket(\ZMQ::SOCKET_PULL);
$pull->bind('tcp://127.0.0.1:5050'); // Binding to 127.0.0.1 means the only client that can connect is itself
$pull->on('message', array($routeOneApp, 'onServerMessage'));

Finally, the server is started:

//run
$loop->run();

This works perfectly fine as long as i am binding only one of the ratchet apps to the ZMQ socket. However, i would like to be able to separately push messages to both of the Ratchet apps. For this purpose i thought of binding two ZMQ sockets to different routes like:

$pullOne->bind('tcp://127.0.0.1:5050' . $routeOne); // Binding to 127.0.0.1 means the only client that can connect is itself
$pullOne->on('message', array($routeOneApp, 'onServerMessage'));

and

$pullTwo->bind('tcp://127.0.0.1:5050' . $routeTwo); // Binding to 127.0.0.1 means the only client that can connect is itself
$pullTwo->on('message', array($routeTwoApp, 'onServerMessage'));

However, this leads to an error message from ZMQ when binding the second socket, saying the given address is already in use.

So the question is, is there any other way to use routes over a ZMQ socket? Or should i use other means to distinguish between messages for the separate Ratchet apps, and if so, what would be a good solution? I thought about binding to 2 different ports, but figured that would be a pretty ugly solution?!

Midbrain answered 7/2, 2017 at 17:10 Comment(2)
are you sure you want to bind() when in PULL? Usually in ZMQ the PUSH side binds(), the PULL side connects().Germin
good question, I copied the bind from some tutorial I found on the internet and it seems to work. I'll have a look if connect() works aswell.Midbrain
S
2

In general in TCP packets are identified by the 4 tuple (sender ip, sender port, receiver ip, receiver port).

When a incoming packet reaches the network layer, it is forwarded to the appropriate application by looking at the receiver ip and port. If you use the same pair for both the apps, it will be impossible for the layer to decide whom to send it to when a connection comes in.

One solution would be to bind a single connection and the write a common handler that looks at the incoming content and then decides (I assume you have some logic) to differentiate the incoming connections to the different instances and then invokes the corresponding handler. The handler can get the connection object and can handle the connection hence forth.

If both your instances are identical and it doesn't matter who gets the request then you can just randomly forward the new connection to any of the handler.

Edit: I have tried to answer the question irrespective of the application type (Racket/ZMQ etc) because the issue you are trying to address is a fundamental one common to any network application.

For this case since you have two apps running and want to listen on the same port, you can have a common handler which can look at the request URL and forward the connection to the appropriate handler.

The request URL can be obtained using

$querystring = $conn->WebSocket->request->getQuery();

Now the clients can connect using

ws://localhost:5050/app1 
and
ws://localhost:5050/app2

Your different apps can now handle these connections separately.

Spieler answered 28/2, 2017 at 19:34 Comment(8)
The thing that makes me question this is the fact that Ratchet uses multiple "apps" that are all bound to the same port. Obviously they all connect to the same websocket and are sent around using IDs or some other methodology. In truth, it would almost be better to write your own JSON protocol that sends messages based on AppID or something similar, but it seems that there is already a method to this and the poor Ratchet documentation doesn't outline it well.Oleic
Once you say multiple "apps" it means you are talking about application layer. It is synonymous to having multiple modules inside a http server like apache. You need some ID (as you mentioned) to discriminate requests to different apps. In case of an HTTP server this is done by the URL. If Rachet documentation doesn't document it well, it would be fine to to implement your own protocol on top of the connection provided by Rachet.Spieler
You don't necessarily need to go to JSON. Remember that Websocket connections also start as regular HTTP connections. So you can use the URL to differentiate. You will have to find the way in the documentation to get the URL used for the websocket handshake. That is if Rachet gives access to that.Spieler
Found it, you can access the URL sent by the client using $querystring = $conn->WebSocket->request->getQuery(); This query string can now be used to invoke the appropriate handler. I assume your client will connect to different URLs like localhost:5050/app1 and localhost:5050/app2Spieler
Thanks for the detailed information. If I'm not mistaken the query you can get through Ratchet router only works for the websocket connections between client and server (since the $conn object is only available inside the Ratchet app). What I wanted to do is distinguish between routes before any of the Ratchet apps is called. Hence it does make sense to me that that might not be possible with a simple tcp connection between apache and ratchet websocket server using ZMQ.Midbrain
Yes, that is my understanding.Spieler
In the end I ended up sending the route as part of the (JSON) payload over the tcp connection and implementing my own simpel router to decide which app to call. Works like a charm up until now!Midbrain
That seems like a very easy way to implement.Spieler

© 2022 - 2024 — McMap. All rights reserved.