I have a web application hosted on AWS written in Laravel/PHP ; i need to let the web application to talk with a socket server listening on port 9090 in a server placed in the same internal network (not in AWS) of the browsers that use the webapp; the socket server DOES NOT support websocket but only raw sockets; how can the client connect to the socket server? Do i need to write client programs in python/other lang to be installed on client machines or is there a better way?
-
nodejs could be an option for you – popeye Oct 25 '17 at 13:01
-
1You can write a pair of small proxy servers: (1) one to connect to the raw socket and relay messages to the (2) second websocket server that your clients connect to. Look into [Ratchet](http://socketo.me/) docs. – Dmitry Oct 25 '17 at 20:08
-
How does the socket server work? Is it bidirectional request/response? Or unidirectional; server emits events and clients only listen or clients send data and the server only stores/processes the data? – Dmitry Oct 26 '17 at 07:00
-
@Dmitry server gets command and emits a OK/KO as a response; the server author told me it's a raw socket server that does not support websocket, so i'm thinking how to consume it – Cris Oct 26 '17 at 07:17
-
1`Do i need to write client programs or is there a better way?` - you can write a proxy server like a mentioned in the 1st comment. I have a solution for something similar written with ReactPHP and Ratchet. I'm thinking how to adopt it to what you need. – Dmitry Oct 26 '17 at 08:19
2 Answers
Yes, you need to write client program/script which would listen to the socket and using the script you can manipulate front-end pages.
I would recommend Javascript code which would listen to your web socket at 9090 and based on the received data, you can make changes on the web page.
Here is the sample code for your reference.
<script src="http://localhost:8080/socket.io/socket.io.js"></script>
<script>
// Create SocketIO instance, connect
var socket = new io.Socket();
socket.connect('http://127.0.0.1:8080');
// Add a connect listener
socket.on('connect',function() {
console.log('Client has connected to the server!');
});
// Add a connect listener
socket.on('message',function(data) {
console.log('Received a message from the server!',data);
// This `data` can be used to show on the page or anything else.
});
// Add a disconnect listener
socket.on('disconnect',function() {
console.log('The client has disconnected!');
});
// Sends a message to the server via sockets
function sendMessageToServer(message) {
socket.send(message);
};
</script>

- 6,558
- 1
- 20
- 33
-
Thank you, but if i've well understood socket.io uses websocket that is not supported in the socket i'm trying to connect to. – Cris Oct 25 '17 at 13:06
-
-
I've asked to the author of the socket server that said it does not support websocket but only raw sockets. – Cris Oct 25 '17 at 13:11
-
1This is the raw socket. Try this out. I am sure this gonna work. – Himanshu Upadhyay Oct 25 '17 at 13:15
-
-
Browsers don't support raw TCP sockets. Browser only support HTTP and WebSockets. WebSockets need special initialization using upgrade request (`Upgrade: websocket`) and they also have their own packet framing. Sure both HTTP and WebSockets use raw sockets underneath, but every protocol on the Internet use them too. – Dmitry Oct 26 '17 at 18:15
-
So we'll crate a pair of processes; (1) a WebSocket server which will be used by browser clients to connect to, (2) a raw socket client which will connect to the raw socket server. These 2 processes will exchange messages using Zero Messaging Queue.
You'll need php-cli
, php-zmq
, libevent
(optional but strongly recommended) and libevent PHP wrapper installed on your system. You'll need Ratchet (cboden/ratchet
), react/zmq
, react/promise
(and maybe something else I forgot) installed using composer. I recommend you create a Proxy
directory in Laravel's app/
directory. So the namespace
is app\Proxy
according to PSR-4.
ReactPHP uses event loops to emulate user space threading (pretty much the same as V8 in node.js does) so you'll see a lot of loops in the code.
Disclaimer:
I did not test this code. It may require some debugging. But it will give you a good idea on what you should do. If you find bugs feel free to comment or just edit and fix the code. Update: I tested it with PHP test server and it works as expected.
websocket_server_bootstrapper.php
<?php
namespace app\Proxy;
require dirname ( dirname ( __DIR__ ) ) . '/vendor/autoload.php';
require 'WebsocketServer.php';
//create an event loop
$loop = \React\EventLoop\Factory::create ();
//pass it to React's wrapper class of PHP's ZMQContext native class
$context = new \React\ZMQ\Context ($loop);
//create an instance of the WebsocketServer class, the code for it is below
$pusher = new WebsocketServer($loop, $context);
//create a ZMQ pull socket which the WebsocketServer class will use to
//get messages from the raw socket client (the code below)
$pull = $context->getSocket ( \ZMQ::SOCKET_PULL );
//bind it to inter-process communication on Linux shared memory
$pull->bind ( 'ipc:///dev/shm/websock0' );
//when you get a message from socket client run this method on WebsocketServer
$pull->on ( 'message', [
$pusher,
'onExternalMessage'
]);
//create a Ratchet app
$server = new \Ratchet\App ( 'localhost', 8090, '0.0.0.0', $loop );
//wrap our WebsocketServer class in Ratchet's WsServer
$wrappedPusher = new \Ratchet\WebSocket\WsServer($pusher);
//give it a route on your website (now it's root)
$server->route ( '/', $wrappedPusher, array (
'*'
) );
//start event loop's infinite loop
$loop->run ();
If you don's want to use ipc
for ZMQ messaging you can use TCP etc. read more in the ZMQ book.
WebsocketServer.php
First of all, what this class does is it accepts WebSocket connections and puts them in $clients
(which is an\SplObjectStorage
) and $cnToConnection
(which is an array) class properties. $cnToConnection
is "connection number to connection" associative array which I use as an index to find a connection quickly. I use connection number to pass it to the raw socket client so when the client get a response from the raw socket server I will know to which connection I should send the response.
<?php
namespace app\Proxy;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use React\EventLoop\LoopInterface;
use React\ZMQ\Context;
class WebsocketServer implements MessageComponentInterface{
/**
* @var \SplObjectStorage<CustomConnection,int> $clients these are
* your clients connected from browsers
*/
protected $clients;
/**
* @var ConnectionInterface[]
*/
protected $cnToConnection;
/**
* @var \React\ZMQ\SocketWrapper
*/
protected $rawSockPusher;
/**
* @var LoopInterface $loop
*/
protected $loop;
/**
* @var int
*/
protected $lastConnectionNumber = 0;
public function __construct(LoopInterface $loop, Context $context)
{
$this->loop = $loop;
$this->clients = new \SplObjectStorage;
//create a push socket from the same ZMQ
//context we used in the bootstrapper
$this->rawSockPusher = $context->getSocket(\ZMQ::SOCKET_PUSH);
$this->rawSockPusher->connect('ipc:///dev/shm/raw_sock_proxy0');
}
public function onOpen(ConnectionInterface $conn)
{
//increment lastConnectionNumber from 0 up to 10M and then back to 0
//I doubt you'll have more then 10M open connections :)
$con_number = $this->lastConnectionNumber++ % 10000000;
//$con_number is the key, $conn is the value
$this->cnToConnection[$con_number] = $conn;
//$conn is the key, $con_number is the value
$this->clients->attach($conn, $con_number);
}
/**
* Get message from browser client.
* @param ConnectionInterface $from
* @param string $msg
*/
public function onMessage(ConnectionInterface $from, $msg)
{
//get connection number
$cn = $this->clients->offsetGet($from);
//put cn and the message in an array, serialize
//and send to the raw socket client
$this->rawSockPusher->send(serialize(['cn' => $cn, 'data' => $msg]));
}
public function onClose(ConnectionInterface $conn)
{
//on closing the connection remove it from both collections
$cn = $this->clients->offsetGet($conn);
$this->clients->detach($conn);
unset($this->cnToConnection[$cn]);
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
// TODO: Implement onError() method.
}
public function onExternalMessage($message)
{
//get the response from the raw socket client
$unserialized_mess = unserialize($message);
//find the connection by connection number and send the response
$this->cnToConnection[$unserialized_mess['cn']]
->send($unserialized_mess['data']);
}
}
Note: I wrote this code based on Ratchet 0.3.6 which did not support server to client pinging. Now Ratchet 0.4 is out and it does support pinging. You need to ping clients to know they are still there. If client does not close the connection properly you'll have dead connections in your $clients
and $cnToConnection
collections. Read about pinging in Ratchet docs at http://socketo.me/.
raw_socket_client_bootstrapper.php
<?php
namespace app\Proxy;
require dirname ( dirname ( __DIR__ ) ) . '/vendor/autoload.php';
require 'RawSocketClient.php';
$loop = \React\EventLoop\Factory::create ();
$context = new \React\ZMQ\Context ($loop);
new RawSocketClient($loop, $context);
$loop->run();
There is nothing new here compared to websocket_server_bootstrapper.php
RawSocketClient.php
<?php
namespace app\Proxy;
use React\EventLoop\LoopInterface;
use React\ZMQ\Context;
use React\Socket\Connector;
use React\Socket\ConnectionInterface;
class RawSocketClient
{
/**
* @var LoopInterface $loop
*/
protected $loop;
/**
* @var Connector
*/
protected $connector;
/**
* @var \ZMQSocket $websockPush
*/
protected $websockPush;
public function __construct(LoopInterface $loop, Context $context)
{
$this->loop = $loop;
$pull = $context->getSocket(\ZMQ::SOCKET_PULL);
$pull->bind('ipc:///dev/shm/raw_sock_proxy0');
$pull->on('message', [
$this,
'onExternalMessage'
]);
$this->connector = new Connector($this->loop, [
'tcp' => true,
'tls' => false,
'unix' => false
]);
$this->websockPush = $context->getSocket(\ZMQ::SOCKET_PUSH);
$this->websockPush->connect('ipc:///dev/shm/websock0');
}
public function onExternalMessage($message)
{
//unserialize the message from the WebSocket server
$unserialized_mess = unserialize($message);
//connect to the raw socket server
$this->connector->connect('tcp://raw_socket_server_address:port_number')
//connect() returns a promise
->then(function (ConnectionInterface $connection) use ($unserialized_mess) {
//when connected register a handler for the onData event
$connection->on('data', function ($data) use ($unserialized_mess, $connection) {
//receive the response and send it together with the connection number
$this->websockPush->send(serialize(['data' => $data, 'cn' => $unserialized_mess['cn']]));
$connection->close();
});
//send your message to the raw server
$connection->write($unserialized_mess['data']);
}, function ($error) {
//TODO handle error
});
}
}
Note: I open and close a connection on every message from the WebSocket server. May be you can reuse these connections by creating $cnToConnection
in this class too. I'll leave it to you.
Running this thing
You run this processes though CLI. Simply by running php websocket_server_bootstrapper.php
and php raw_socket_client_bootstrapper.php
. You can run them in on start up from /etc/rc.local
or through something like Supervisord. Note: you can run more then one instance of raw_socket_client_bootstrapper.php
- ZMQ will take care of load balancing between instances. So if you need to do some processing do it in the raw socket client.