I want to create a PHP script that connects to a websocket server and then serves the responses from the remote server on a local websocket server. Basically, a websocket relay. I have working client code and I have working server code.
The problem comes when I try to combine the client with the server. I've tried taking my client code and putting it in my server code. It works except that the client is blocking so that if I disconnect from it, I can't reconnect. It happens like this:
I start my code -> server code opens local port and serves websocket -> I connect to local port with websocket testing tool -> client code connects to remote websocket server and relays responses to me -> I disconnect my tool -> client code never returns so server code can't accept new connections anymore.
My code uses the following composer packages:
"cboden/ratchet": "^0.4.0",
"ratchet/pawl": "^0.3.1"
You can see that I have commented out the client part that connects to the remote websocket server. This is the part that blocks.
<?php
// Make sure composer dependencies have been installed
require '../web/vendor/autoload.php';
require_once('../web/usersc/includes/custom_functions.php');
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use WebSocket\Client;
/**
* chat.php
* Send any incoming messages to all connected clients (except sender)
*/
class MyChat implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
/*
$loop = React\EventLoop\Factory::create();
$reactConnector = new React\Socket\Connector($loop, [
'dns' => '8.8.8.8',
'timeout' => 10
]);
$connector = new Ratchet\Client\Connector($loop, $reactConnector);
$connector('wss://example.com/websocket', [], ['Origin' => 'http://localhost'])
->then(function(Ratchet\Client\WebSocket $conn) {
$conn->on('message', function(\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
echo "Received: {$msg}\n";
//$conn->close();
});
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed ({$code} - {$reason})\n";
});
$conn->send('Hello World!');
}, function(\Exception $e) use ($loop) {
echo "Could not connect: {$e->getMessage()}\n";
$loop->stop();
});
$loop->run();
*/
}
public function onMessage(ConnectionInterface $from, $msg) {
foreach ($this->clients as $client) {
if ($from != $client) {
$client->send($msg."sdfsfsf");
}
}
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e) {
$conn->close();
}
}
// Run the server application through the WebSocket protocol on port 8080
$app = new Ratchet\App('localhost', 8080);
$app->route('/chat', new MyChat, array('*'));
$app->run();
Related
i am using php ratchet websockets on my shared server on one domain the ratchet websockets is working perfectly but when i run sockets on my other domain it give me some tcp errors both of my domains are on the same server.
That is my socket.php code many time I changes my port number also but nothing happended
<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
require 'chat.php';
$loop = React\EventLoop\Factory::create();
// Run the server application through the WebSocket protocol on port 8888
$app = new Ratchet\App("example.com", 8888, '0.0.0.0',$loop);
$app->route('/chat', new Chat, array('*'));
$app->run();
$app->enableKeepAlive($app->loop, 10);
?>
and here is my chat.php code
<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Chat implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->clients = array();
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients[$conn->resourceId] = $conn;
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
$numRecv = count($this->clients) - 1;
echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
, $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
foreach ($this->clients as $key => $client) {
if ($from !== $client) {
// The sender is not the receiver, send to each client connected
$client->send($msg);
}
}
// Send a message to a known resourceId (in this example the sender)
$client = $this->clients[$from->resourceId];
$client->send("Message sent to $numRecv users.");
}
public function onClose(ConnectionInterface $conn) {
// The connection is closed, remove it, as we can no longer send it messages
unset($this->clients[$conn->resourceId]);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
?>
now when i run php socket.php on command line than it is not doing anything and when i run the socket.php url it shows this page isn't workingbelow is my composer.json code
{
"require": {
"cboden/ratchet": "^0.4.2",
"textalk/websocket": "^1.3"
}
}
I also installed mbstring and iconv in my domain.
The error is:
PHP Fatal error: Uncaught RuntimeException: Failed to listen on
"tcp://127.0.0.1:8843": Address already in use in
/home/example.com/public_html/test/rachet/vendor/react/socket/src/TcpServer.php:164
I am using Ratchet for Websockets in my application. I am running the Websocket server in supervisor. The application runs well for sometime even when there is high traffic but after some time the server responds with a 503 service unavailable error when I try to access the application in the browser until I restart supervisor and It runs fine again for some time. This happens at times when there is low traffic to the sever so I do not think it a traffic problem.
Could I have missed a configuration somewhere during deployment of Ratchet because it is the only program that I am running in supervisor.
Thanks for your assistance.
I solved this by adding gc_collect_cycles() to the onClose function.
My code now looks like this:
<?php
//Pusher
namespace MyApp;
use Ratchet\ConnectionInterface;
use Ratchet\Wamp\WampServerInterface;
class Pusher implements WampServerInterface
{
//Array of channels with connected clients
protected $subscribed_channels= array();
protected $clients=array();
protected $connections;
public function __construct() {
$this->connections = new \SplObjectStorage;
}
//Accepts client connections and adds the channel subscribed to in the subscribed channels
public function onSubscribe(ConnectionInterface $conn, $channel)
{
$channel_id=$channel->getId();
$this->subscribed_channels[$channel_id] = $channel;
$connection_id=$conn->resourceId;
$this->clients[$connection_id]=array('channel'=>$channel);
$channel_subscriptions=$channel->count();
//echo "Client {$connection_id} has subscribed to {$channel_id} channel. {$channel_subscriptions} Cleints Subscribed \n";
}
//Accepts data as json containing the channel being pushed to, and the data being pushed.
public function onPush($request)
{
$request_array = json_decode($request, true);
if(!isset($request_array['channel']))
{
echo"Channel not sent by app \n";
return;
}
//Check if data has been sent by app
if(!isset($request_array['data']))
{
echo"Data not sent by app \n";
return;
}
// If the channel being pushed to has no subscribers, don't push
if (!array_key_exists($request_array['channel'], $this->subscribed_channels))
{
echo"{$request_array['channel']} channel has no subscribers \n";
return;
}
$channel = $this->subscribed_channels[$request_array['channel']];
// Foward the request to all the subscribers in the channel
$data=json_encode($request_array['data']);
$channel->broadcast($data);
//echo "Pushing data to {$request_array['channel']} channel at ".strftime('%Y-%m-%d %H:%M:%S')."\n";
}
public function onUnSubscribe(ConnectionInterface $conn, $channel)
{
$connection_id=$conn->resourceId;
$channel_id=$channel->getId();
$channel_subscriptions=$channel->count()-1;
if($channel_subscriptions==0)
{
unset($this->subscribed_channels[$channel_id]);
}
//echo "Client {$conn->resourceId} has disconnected from {$channel_id} channel. {$channel_subscriptions} Clients subscribed \n";
}
public function onOpen(ConnectionInterface $conn)
{
$this->connections->attach($conn);
}
public function onClose(ConnectionInterface $conn)
{
$connection_id=$conn->resourceId;
$channel=$this->clients[$connection_id]['channel'];
unset($this->clients[$connection_id]);
$this->onUnSubscribe($conn,$channel);
$this->connections->detach($conn);
gc_collect_cycles();
}
public function onCall(ConnectionInterface $conn, $id, $channel, array $params)
{
// In this application if clients send data it's because the user hacked around in console
$conn->callError($id, $channel, 'You are not allowed to make calls')->close();
}
public function onPublish(ConnectionInterface $conn, $channel, $event, array $exclude, array $eligible)
{
// In this application if clients send data it's because the user hacked around in console
$conn->close();
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error has occurred: {$e->getMessage()}\n";
}
}
//Web socket server code
<?php
require dirname(__DIR__) . '/vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$pusher = new MyApp\Pusher;
// 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:5555'); // Binding to 127.0.0.1 means the only client that can connect is itself
$pull->on('message', array($pusher, 'onPush'));
//Replace the variables $my_ip and $my_port with your server $ip and $port.
$webSock = new React\Socket\Server('$my_ip:$my_port', $loop);
$webServer = new Ratchet\Server\IoServer(
new Ratchet\Http\HttpServer(
new Ratchet\WebSocket\WsServer(
new Ratchet\Wamp\WampServer(
$pusher
)
)
),
$webSock
);
$loop->run();
//My php app code
//You will need the zmq php module in your php for this to work
function push_to_socket($channel,$data)
{
$message = array(
'channel' => $channel
, 'data' => $data
);
$context = new ZMQContext();
$socket = $context->getSocket(ZMQ::SOCKET_PUSH, 'my pusher');
$socket->connect("tcp://localhost:5555");
$socket->send(json_encode($message));
}
//My Javascript code
//You need AutobahnJS for this to work.
//You might also need jQuery or else modify the code to eliminate jQuery syntax
//Replace 'channel' with a channel/topic name you want to use.
<script type="text/javascript">
$(document).ready(get_connected);
function get_connected()
{
var hasWs = 'WebSocket' in window || 'MozWebSocket' in window;
if (hasWs)
{
conn = new ab.Session('wss:{my_domain}/wss',
function()
{
conn.subscribe('channel', function(topic,message){
//Process your message
});
},
function() {
//console.warn('Connection closed');
get_connected();
},
{'skipSubprotocolCheck': true}
);
}
}
</script>
I am trying to achieve mechanism where PHP pushes message to RabbitMQ (I don't want RabbitMQ to be directly exposed to user), RabbitMQ connects to RatchetPHP and Ratchet broadcasts it via websocket connections to users.
The issue I have is with accually making Ratchet server to simultanously listen for queue messages and transfer them further. Ratchet documentation assumes using ZeroMQ and after a long search through outdated documentations and libraries which do not have such methods anymore (eg. React\Stomp) I need fresh eyes from someone who has experience with these solutions.
What I have is pusher.php (standard example from RabbitMQ docs):
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('hello', false, false, false, false);
$msg = new AMQPMessage('Hello World!');
$channel->basic_publish($msg, '', 'hello');
echo " [x] Sent 'Hello World!'\n";
$channel->close();
$connection->close();
Just to simplify reproducing scenario I include also Chat class:
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
class Chat implements MessageComponentInterface
{
protected $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $connection)
{
// Store the new connection to send messages to later
$this->clients->attach($connection);
echo "New connection! ({$connection->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg)
{
$numRecv = count($this->clients) - 1;
echo sprintf('Connection %d sending message "%s" to %d other connection%s'."\n"
, $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
foreach($this->clients as $client)
{
/** #var \SplObjectStorage $client */
if($from !== $client)
{
// The sender is not the receiver, send to each client connected
$client->send($msg);
}
}
}
public function onClose(ConnectionInterface $conn)
{
// The connection is closed, remove it, as we can no longer send it messages
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
And Ratchet server.php (standard Ratchet example and RabbitMQ receiver example):
use PhpAmqpLib\Connection\AMQPStreamConnection;
use Src\Chat;
// RABBIT_RECEIVER
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('hello', false, false, false, false);
echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";
$callback = function ($msg)
{
echo " [x] Received ", $msg->body, "\n";
};
$channel->basic_consume('hello', '', false, true, false, false, $callback);
while(count($channel->callbacks))
{
$channel->wait();
}
$channel->close();
$connection->close();
// RABBIT_RECEIVER END
$server = new \Ratchet\App('sockets.dev');
$server->route('/', new Chat());
$server->run();
Current versions are basically 2 separate mechanisms listening for messages and they work great alone (so no issue there) except that they block each other and do not transfer messages between.
Question is how to make server.php to make RabbitMQ receive message and plug it into running Ratchet server.
I figured it out so adding answer for posterity. Solution is not fully real time but it is very close, seems to have good performance and is non-blocking for Ratchet websocket server. Solution was in Ratchet custom loop and addPeriodicTimer method.
So code for server.php should look like this:
$loop = React\EventLoop\Factory::create();
$chat = new Chat($loop);
$server = new \Ratchet\App('sockets.dev', 8080, '127.0.0.1', $loop);
$server->route('/', $chat);
$server->run();
And Chat.php class (only __constructor because rest is the same):
public function __construct(LoopInterface $loop)
{
$this->loop = $loop;
$this->clients = new \SplObjectStorage();
$this->loop->addPeriodicTimer(0, function ()
{
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('hello', false, false, false, false);
echo ' [*] Checking for for messages from RabbitMQ', "\n";
$max_number_messages_to_fetch_per_batch = 10000;
do
{
$message = $channel->basic_get('hello', true);
if($message)
{
foreach($this->clients as $client)
{
$client->send($message->body);
}
$max_number_messages_to_fetch_per_batch--;
}
}
while($message && $max_number_messages_to_fetch_per_batch > 0);
$channel->close();
$connection->close();
});
}
Initiating Ratchet server will attach periodic event (in example every 0 sec) which will check RabbitMQ for new messages and process them.
Just for better performance control allowing websocket to take a breath number of messages from RabbitMQ processed in one batch was limited to 10k. Processed messages are removed from queue and next batch is processed in next iteration.
You can also fine tune frequency of updates with addPeriodicTimer interval parameter. 0 seconds is the closest to real time you will get but it may not be required for your needs and can be changed to higher value.
I have a PHP based Websocket server that is used to connect web clients. The WebSocket server itself serving as a Client for WhatsApp. I want when server goes up, it then make a call to WhatsApp and make a live connection. Below is my WebSocket Class. Since there's an infinite While loop, other things are not being called; like connecting a client.
How do I make it that when Web Socket server loads up, it makes a connection to WhatsApp server as well and then let other clients to connect Web Socket server
Chat.php
#!/usr/local/bin/php
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
require_once __DIR__.'/vendor/autoload.php';
require_once 'vendor/whatsapp/chat-api/src/whatsprot.class.php';
require_once 'vendor/whatsapp/chat-api/src/events/MyEvents.php';
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Guzzle\Http\Client;
use Guzzle\Http\EntityBody;
use Guzzle\Http\Message\Request;
use Guzzle\Http\Message\Response;
class Chat implements MessageComponentInterface {
protected $clients;
protected $message;
protected $client_info = []; //To hold which agent was connected
protected $username = 'xx';
protected $password = 'xx=';
protected $nickname = 'xx Number';
protected $target = "xx";
protected $debug = false;
public function __construct() {
$this->clients = new \SplObjectStorage;
print("..Server is coming up..\n");
echo "[] Logging in as '$this->nickname' ($this->username)\n";
$w = new WhatsProt($this->username, $this->nickname, $this->debug,true,__DIR__.'/wadata/');
$events = new MyEvents($w);
$events->setEventsToListenFor($events->activeEvents);
$w->connect();
$w->loginWithPassword($this->password);
print("..Accepting WhatsApp Messages...\n");
while (1) {
$w->pollMessage();
}
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients->attach($conn);
//Identity Message
$this->message = json_encode(['STATUS'=> 'CONNECTED','CLIENT_ID' => $conn->resourceId]);
$conn->send($this->message);
$this->message = json_encode(['STATUS' => 'NEW', 'TYPE'=> 'TEXT','MESSAGE' => "This is by customer"]);
//Actual WhatsApp message
$conn->send($this->message);
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
$numRecv = count($this->clients) - 1;
print("CLIENT SAYS:- ".$msg."\n");
foreach ($this->clients as $client) {
if ($from === $client) {
$msg = 'WA Server responds:- '.$msg;
$client->send($this->message);
}
}
}
public function onClose(ConnectionInterface $conn) {
// The connection is closed, remove it, as we can no longer send it messages
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8080
);
$server->run();
I managed to create a web service that listens on port 80 for http requests and handles ajax calls and also long polling.
But I'm stuck at creating a similar websocke server in the same php file
<?php
require_once('vendor/autoload.php');
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Chat implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients->attach($conn);
$conn->send('aa');
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
$numRecv = count($this->clients) - 1;
echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
, $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
foreach ($this->clients as $client) {
if ($from !== $client) {
// The sender is not the receiver, send to each client connected
$client->send($msg);
}
}
}
public function onClose(ConnectionInterface $conn) {
// The connection is closed, remove it, as we can no longer send it messages
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
$loop = React\EventLoop\Factory::create();
$swsa=new Chat;
$webSock = new React\Socket\Server($loop);
$webSock->listen(8687, '127.0.0.1'); // Binding to 0.0.0.0 means remotes can connect
$webServer = new Ratchet\Server\IoServer(
new Ratchet\Http\HttpServer(
new Ratchet\WebSocket\WsServer(
$swsa
)
),
$webSock
);
$app = function ($request, $response) {
//some code here to manage requests
$response->writeHead(200,array('Content-Type'=>'application/json'));
$response->end(json_encode(array('ok'=>'ok')));
};
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket, $loop);
$http->on('request', $app);
$socket->listen(8485);
$loop->run();
Code in browser:
var wsUri = "ws://127.0.0.1:8687/";
websocket = new WebSocket(wsUri);
This triggers an error in the browser
Firefox can't establish a connection to the server at ws://127.0.0.1:8687/
Your IoServer initialization is incorrect. If you check the code for IoServer class, you will find:
public function __construct(MessageComponentInterface $app,ServerInterface $socket, LoopInterface $loop = null)
and
public function run() {
if (null === $this->loop) {
throw new \RuntimeException("A React Loop was not provided during instantiation");
}
// #codeCoverageIgnoreStart
$this->loop->run();
// #codeCoverageIgnoreEnd
}
The IoServer class object has no loop assigned to it and hence it fails to execute. Try passing loop as your third argument.