I create a web socket server from this tuts https://www.sitepoint.com/how-to-quickly-build-a-chat-app-with-ratchet/
now I want to know how can I send a message to the specific connection. these are my code. in these codes send a message to all connection but i want to know which connection send me a message then send a message to that connection.
my client js
(function(){
var user;
var messages = [];
function updateMessages(msg){
messages.push(msg);
}
var conn = new WebSocket('ws://127.0.0.1:4510');
conn.onopen = function(e) {
console.log("Connection established!");
conn.onmessage = function(e) {
var msg = JSON.parse(e.data);
alert(msg);
updateMessages(msg);
};
conn.onclose = function () {
// conn.close();
}; // disable onclose handler first
var i = 0;
$('#start').click(function(){
user = $('#user').val();
var msg = {
"name" : 'start'
};
updateMessages(msg);
conn.send(JSON.stringify(msg));
});
};
})();
and my php server file
<?
protected $clients;
public $i = 0;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
foreach ($this->clients as $client) {
if ($from !== $client) {
$client->send($rsid);
}
}
}
public function onClose(ConnectionInterface $conn) {
$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();
}
I have the similar issue days ago, and I finally find a good way to handle all the connections in a good way:
Instead of doing
var conn = new WebSocket('ws://127.0.0.1:4510');
I pass the userId as an URL query like that:
var conn = new WebSocket('ws://127.0.0.1:4510/?id=123');
On the server-side, store the userId and incoming connection as a key-value pair in PHP array. When you want to send to specific user, just index that array and you will get the connection.
$query = $conn->httpRequest->getUri()->getQuery();
//get query from URL like ws://127.0.0.1:8080/?id=123456
$query_list = explode("&", $query);
$user_id = trim(substr($query_list[0], 3));
$this->users[$user_id] = $conn;
If you also want to limit one connection per user and notify the old connection if the same user login elsewhere, you can refer to my code here:
https://github.com/tli4/ratchet-practice
Related
I use workerman/workerman for websockets.
The main question – how to send messages on websocket closing?
Also I tried to send messages to other connections. No result too.
public function run()
{
$this->ws = new Worker(
'websocket://0.0.0.0:' . $_ENV['WS_PORT'],
['ssl' => [
'local_cert' => $_ENV['WS_CERTIFICATE_CRT'],
'local_pk' => $_ENV['WS_CERTIFICATE_KEY'],
'verify_peer' => false,
]]
);
$this->ws->count = 1;
$this->ws->transport = 'ssl';
$this->ws->onConnect = function ($connection) {
$this->onConnect($connection);
};
$this->ws->onMessage = function ($connection, $data) {
$this->onMessage($connection, $data);
};
$this->ws->onClose = function ($connection) {
$this->onClose($connection);
};
Worker::runAll();
}
This doesn't work
private function onClose($connection)
{
$connection->send("Hello");
}
Looking at the source code of workerman/workerman we can see that the onClose event is implemented as follows:
// Close socket.
try {
#fclose($this->socket);
} catch (Throwable) {
}
$this->status = self::STATUS_CLOSED;
// Try to emit onClose callback.
if ($this->onClose) {
try {
($this->onClose)($this);
} catch (Throwable $e) {
$this->error($e);
}
}
As you can see, onClose is emitted AFTER the socket has been closed.
So you will not be able to use that socket in your onClose handler anymore.
A better solution would be that the client sends some sort of "close" packet to the server.
The server can then run some logic and close the connection after it is done.
I am new at Ratchet Websocket and currently working on a simple chat application by using Ratchet Websocket library. Using the directions on the socketo.me site, I am trying to make websockets work by using the Chat.php and Server.php provided in http://socketo.me/docs/hello-world. My goal is want to send json string and update the database in the table (update chat messages). However, fa
This is the Chat.php which is the class that implements the MessageComponentInterface.
Chat class
<?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);
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();
}
}
I added a function, so that the messages are inserted into the database.
savemessage function
public function savemessage($data)
{
$this->connect = 'mysql:host=' . $this->db_host .
';dbname=' . $this->db_name;
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try {
$this->db_handler = new PDO($this->connect, $this->db_user, $this->db_pass, $options);
} catch (PDOException $e) {
$this->error = $e->getMessage();
echo $this->error;
}
$query = "INSERT INTO chatrooms (id, uuid, msg, created_on) VALUES (default, :uuid, :msg, :created_on)";
$this->statement = $this->db_handler->prepare($query);
$type = PDO::PARAM_STR;
$this->statement->bindValue(':uuid', $data['uuid'],$type);
$this->statement->bindValue(':msg', $data['msg'],$type);
$this->statement->bindValue(':created_on', $data['created_on'],$type);
return ($this->statement->execute());
}
Chat class(edited section) The function of onMessage is edited, in order to insert the data.
......
$data = json_decode($msg, true);
if ($this->savemessage($data)) {
echo 'Saved message to DB';
} else {
echo 'Failed to save message';
}
$data['dt'] = date("d-m-Y h:i:s");
foreach ($this->clients as $client) {
/*
if ($from !== $client) {
// The sender is not the receiver, send to each client connected
$client->send($msg);
}*/
if ($client !== $from) {
$client->send($data['msg']);
}
}
......
Screenshot of error
As you can see from the screenshot, the connection is successful. The json string is successfully sent. However, there is a fatal error on the prepare function.
This line of code
$this->statement = $this->db_handler->prepare($query);
I have tried to call this function from other classes.(Originally the save message function is from other classes. The entire project is in MVC structure, but the error is still the same.)
It would be greatly appreciated if anyone have any ideas about what the problem might be or how to fix it. Thanks!
I have a very simple websocket using PHP and Ratchet libraray.
When a user opens a specific page it sends the users id to my socket and it should update the status for that user (at the moment I'm just logging it in the console), like this:
<input type="hidden" value="'.$account_id.'" id="account_id">
<input type="hidden" value="trial" id="request_type">
<script>
$(document).ready(function(){
var conn = new WebSocket('ws://127.0.0.1:8080');
conn.onopen = function(e){
console.log("Connection Opened!");
var account_id = $("#account_id").val();
var request_type = $("#request_type").val();
var data = {account_id: account_id, request_type: request_type};
conn.send(JSON.stringify(data));
}
conn.onclose = function(e){
console.log("Connection Closed!");
}
conn.onmessage = function(e) {
var data = JSON.parse(e.data);
console.log(data);
};
conn.onerror = function(e){
var data = JSON.parse(e.data);
console.log(data);
}
})
</script>
Then my socket script is as follows:
set_time_limit(0);
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require dirname(__DIR__) . '../vendor/autoload.php';
class socket implements MessageComponentInterface{
protected $clients;
public function __construct(){
$this->clients = new \SplObjectStorage;
echo 'Server Started.'.PHP_EOL;
}
public function onOpen(ConnectionInterface $socket){
$this->clients->attach($socket);
echo 'New connection '.$socket->resourceId.'!'.PHP_EOL;
}
public function onClose(ConnectionInterface $socket) {
$this->clients->detach($socket);
echo 'Connection '.$socket->resourceId.' has disconnected'.PHP_EOL;
}
public function onError(ConnectionInterface $socket, \Exception $e) {
echo 'An error has occurred: '.$e->getMessage().'!'.PHP_EOL;
$socket->close();
}
public function onMessage(ConnectionInterface $from, $json){
echo 'Connection '.$from->resourceId.' sent '.$json.PHP_EOL;
$data = json_decode($json, true);
$account_id = $data['account_id'];
$request_type = $data['request_type'];
try {
$conn = new PDO("mysql:host=".$db_host.";port:".$db_port.";dbname=".$db_name."", $db_user, $db_pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(PDOException $e){
echo $e->getMessage();
}
foreach ($this->clients as $client) {
if ($from->resourceId == $client->resourceId) {
if($request_type == 'trial'){
// while(true){
$response_array= [];
$stmt = $conn->prepare("SELECT * FROM table WHERE account_id=:account_id AND last_status_change=now()");
$stmt->bindParam(':account_id', $account_id);
$stmt->execute();
$result = $stmt->setFetchMode(PDO::FETCH_ASSOC);
foreach($stmt->fetchAll() as $key=>$value) {
$response_array[$key] = $value;
}
if(!empty($response_array)){
foreach($response_array as $item){
$status = $item['status'];
}
$response = array(
'account_id' => $account_id,
'status' => $status
);
var_dump($response);
$client->send(json_encode($response));
}
// sleep(5);
// }
}
}
}
}
}
$server = IoServer::factory(
new HttpServer(
new WsServer(
new socket()
)
),
8080
);
$server->run();
As it stands it works as expected, but only gives the current status if the status changed at the time when the page was loaded and I will see the status in the console, as soon as I un-comment the while() loop to actually keep checking the status for updates, my socket will do the var_dump() of the result in the command line when there is a status change but nothing gets logged in the client.
I'm new to websockets, I had been doing long polling by having an interval in JS that was sending a fetch() to a PHP script that got the latest DB results but it wasn't very efficient and was causing issues when a large number of clients were active and constantly making requests to the file which was in turn slowing down the DB. So I'm not sure why the while() loop is affecting it like this or if I am even going about this the right way.
A while loop is not how it works. It will block stuff and infinitely and unnecessarily consume resources.
What you want is addPeriodicTimer().
Check periodically for clients that need updates.
Add to your bootstrapping something like this:
$reactEventLoop->addPeriodicTimer(5, function() use $messageHandler, $server {
// Fetch all changed clients at once and update their status
$clientsToUpdate = getUpdatedClients($server->app->clients);
foreach ($clientsToUpdate as $client) {
$client->send(json_encode($response));
}
});
This is much more lightweight than any other method, as you can
Fetch N clients status with a single prepared database query
Update only changed clients periodically
Not put your app in a blocking state
Other resources on Stackoverflow will help you to find the right spot:
How do I access the ratchet php periodic loop and client sending inside app?
Periodically sending messages to clients in Ratchet
replace this line if ($from->resourceId == $client->resourceId) { with if ($from == $client) { this change may look simple but in the example Chat class provided by php ratchet in order avoid sending the message to the sender they have a condition to send messages to clients except the sender, they compared like this if ($from == $client) { only not only an resourceId the entire object itself!
you should be using addPeriodicTimer from Ratchet, although you have to make $clients public in order to place the timer.
Maybe you can place it inside the class and still be private, but I am not sure if it could initiate a timer for every client.
Anyway as you can see, you can create another public function that will actually do the job in the periodic timer(just like while loop)
and then call it once the client is connected and multiple times inside the timerloop,
for that I created also a public account_ids to keep truck of the account ids
Give it a try and let me know
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require dirname(__DIR__) . '../vendor/autoload.php';
class socket implements MessageComponentInterface{
public $clients;
public $account_ids;
public function __construct(){
$this->clients = new \SplObjectStorage;
echo 'Server Started.'.PHP_EOL;
}
public function onOpen(ConnectionInterface $socket){
$this->clients->attach($socket);
echo 'New connection '.$socket->resourceId.'!'.PHP_EOL;
}
public function onClose(ConnectionInterface $socket) {
$this->clients->detach($socket);
echo 'Connection '.$socket->resourceId.' has disconnected'.PHP_EOL;
}
public function onError(ConnectionInterface $socket, \Exception $e) {
echo 'An error has occurred: '.$e->getMessage().'!'.PHP_EOL;
$socket->close();
}
public function onMessage(ConnectionInterface $from, $json){
echo 'Connection '.$from->resourceId.' sent '.$json.PHP_EOL;
$data = json_decode($json, true);
$account_id = $data['account_id'];
$request_type = $data['request_type'];
foreach ( $this->clients as $client ) {
if ( $from->resourceId == $client->resourceId ) {
if( $request_type == 'trial'){
$this->account_ids[$client->resourceId] = $account_id;
$this->checkStatus($client, $account_id);
}
}
}
}
public function checkStatus($client, $account_id){
try {
$conn = new PDO("mysql:host=".$db_host.";port:".$db_port.";dbname=".$db_name."", $db_user, $db_pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(PDOException $e){
echo $e->getMessage();
}
$response_array= [];
$stmt = $conn->prepare("SELECT * FROM table WHERE account_id=:account_id AND last_status_change=now()");
$stmt->bindParam(':account_id', $account_id);
$stmt->execute();
$result = $stmt->setFetchMode(PDO::FETCH_ASSOC);
foreach($stmt->fetchAll() as $key=>$value) {
$response_array[$key] = $value;
}
if ( !empty($response_array) ) {
foreach($response_array as $item){
$status = $item['status'];
}
$response = array(
'account_id' => $account_id,
'status' => $status
);
var_dump($response);
$client->send(json_encode($response));
}
}
}
$socket = new socket();
$server = IoServer::factory(
new HttpServer(
new WsServer(
$socket
)
),
8080
);
$server->loop->addPeriodicTimer(5, function () use ($socket) {
foreach($socket->clients as $client) {
echo "Connection ".$client->resourceId." check\n";
$socket->checkStatus($client, $socket->account_ids[$client->resourceId]);
}
});
$server->run();
I did a webchat using Ratchet, it works perfectly on my computer, if I start the script by using command line, it runs, and if I open the localhost of the chat in another browser, it works perfectly, I can talk with my other browser, then, I did it, I felt wonder, but I tried to make deploy of my chat, and share with my friends to see if it is really working for everyone, and isn't working to everyone, only send the message and nobody can see, so, I don't know if the websocket is really working or if the problem is the server host, because I use 000webhost, maybe it isn't so good using websockets.
Source: chat-server.php
<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Ricardo\Socket\Chat;
require 'vendor/autoload.php';
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8080
);
$server->run();
Source: chat.php(/lib/Ricardo/Socket)
<?php
namespace Ricardo\Socket;
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);
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();
}
}
Source: script.js
var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
//console.log("Connection established!");
};
conn.onmessage = function(e) {
// console.log(e.data);
showMessages('other', e.data);
};
//conn.send('Hello World!');
///////////////////////////////////////////////
var form1 = document.getElementById('chat');
var inp_message = document.getElementById('message');
var inp_name = document.getElementById('name');
var btn_env = document.getElementById('send');
var area_content = document.getElementById('content');
btn_env.addEventListener('click', function(){
if (inp_message.value != '') {
var msg = {'name': inp_name.value, 'msg': inp_message.value};
msg = JSON.stringify(msg);
conn.send(msg);
showMessages('me', msg);
inp_message.value = '';
}
});
function showMessages(how, data) {
data = JSON.parse(data);
console.log(data);
if (how == 'me') {
var img_src = "chat.png";
} else if (how == 'other') {
var img_src = "chat-1.png";
}
var div = document.createElement('div');
div.setAttribute('class', how);
var img = document.createElement('img');
img.setAttribute('src', img_src);
var div_txt = document.createElement('div');
div_txt.setAttribute('class', 'text');
var h5 = document.createElement('h5');
h5.textContent = data.name;
var p = document.createElement('p');
p.textContent = data.msg;
div_txt.appendChild(h5);
div_txt.appendChild(p);
div.appendChild(img);
div.appendChild(div_txt);
area_content.appendChild(div);
}
Well, I sent only these sources, because in my opinion that's important, help, please!
Apparently, ratchet doesn't work on 000webhost, as it requires shell access.
The following has an on error event. How can I determine the specific error?
<?php
$loop = Factory::create();
$socket = new React\Socket\Server($loop);
$socket->on('connection', function (\React\Socket\ConnectionInterface $stream){
$stream->on('data', function($rsp) {
echo('on data');
});
$stream->on('close', function($conn) {
echo('on close');
});
$stream->on('error', function($conn) use ($stream) {
echo('on error');
// How do I determine the specific error?
$stream->close();
});
echo("on connect");
});
$socket->listen('0.0.0.0',1337);
$loop->run();
Looking at the implementation of a ConnectionInterface, React\Socket\Connection, it extends React\Stream\Stream, which uses emit() (which will trigger the callbacks registered with on):
https://github.com/reactphp/stream/blob/c3647ea3d338ebc7332b1a29959f305e62cf2136/src/Stream.php#L61
$that = $this;
$this->buffer->on('error', function ($error) use ($that) {
$that->emit('error', array($error, $that));
$that->close();
});
Thus, the first argument of that function is the error, the second one the $stream:
$stream->on('error', function($error, $stream) {
echo "an exception happened: $error";
// $error will be an instance of Throwable then
$stream->close();
});