I'm trying to build a system where user can subscribe to a category on establishing connection to websocket server and then he will start receiving updates for that category. So far, I have worked with Ratchet and I am able to send message to all the connected clients but the problem is I don't want to send message to all the clients I just want to send the message to the clients who have subscribed the the particular category in which the messages was sent.
PHP Code
Chat.php
<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Chat implements MessageComponentInterface
{
protected $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
}
public function onMessage(ConnectionInterface $conn, $msg)
{
foreach ($this->clients as $client)
{
if($conn !== $client)
$client->send($msg);
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
?>
server.php
<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Chat;
require dirname(__DIR__) . '/Ratchet/vendor/autoload.php';
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8080
);
$server->run();
?>
Client side js code
<script type="text/javascript">
var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
console.log("Connection established!");
};
conn.onmessage = function(e) {
console.log(e.data);
};
</script>
Basically you want a syntax for sending data to the WebSocket, I reccomend using a JSON object to do this. In your WebSocket class you need a local variable called subscriptions and a local variable called users like so.
<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Chat implements MessageComponentInterface
{
protected $clients;
private $subscriptions;
private $users;
public function __construct()
{
$this->clients = new \SplObjectStorage;
$this->subscriptions = [];
$this->users = [];
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
$this->users[$conn->resourceId] = $conn;
}
public function onMessage(ConnectionInterface $conn, $msg)
{
$data = json_decode($msg);
switch ($data->command) {
case "subscribe":
$this->subscriptions[$conn->resourceId] = $data->channel;
break;
case "message":
if (isset($this->subscriptions[$conn->resourceId])) {
$target = $this->subscriptions[$conn->resourceId];
foreach ($this->subscriptions as $id=>$channel) {
if ($channel == $target && $id != $conn->resourceId) {
$this->users[$id]->send($data->message);
}
}
}
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
unset($this->users[$conn->resourceId]);
unset($this->subscriptions[$conn->resourceId]);
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
?>
The javascript to go with that looks a bit like this
<script type="text/javascript">
var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
console.log("Connection established!");
};
conn.onmessage = function(e) {
console.log(e.data);
};
function subscribe(channel) {
conn.send(JSON.stringify({command: "subscribe", channel: channel}));
}
function sendMessage(msg) {
conn.send(JSON.stringify({command: "message", message: msg}));
}
</script>
Note: This code is untested I wrote it on the fly from my experience with Ratchet. Good luck :)
ok, now I am sharing my experience. You Can Send Token and Insert This toke in onOpen (server.php) in the database. You can Insert Chat Token When User Login. Every Time When User Login New Token is generated and updated.
Now Use This Token To Find out user Id in onOpen connection.
var conn = new WebSocket('ws://172.16.23.26:8080?token=<?php echo !empty($_SESSION['user']['token']) ? $_SESSION['user']['token'] : ''; ?>');
conn.onopen = function(e) {
console.log("Connection established!");
};
Now Server.php
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
$querystring = $conn->httpRequest->getUri()->getQuery();
parse_str($querystring,$queryarray);
//Get UserID By Chat Token
$objUser = new \users;
$objUser->setLoginToken($queryarray['token']);
$GetUserInfo = $objUser->getUserByToken();
echo "New connection! ({$conn->resourceId})\n";
//save Chat Connection
// Insert the new connection with the user ID to Match to later if user ID in Table Then Update IT with a new connection
}
Now your Connection is established, then send Message To particular users
Client-Side send USerID with received ID and Message
$("#conversation").on('click', '#ButtionID', function () {
var userId = $("#userId").val();
var msg = $("#msg").val();
var receiverId = $("#receiverId").val();
if ( userId != null && msg != null ){
var data = {
userId: userId,
msg: msg,
receiverId: receiverId
};
conn.send(JSON.stringify(data));
$("#msg").val("");
}
});
Server Side
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');
$data = json_decode($msg, true);
echo $data['userId'];
echo $data['msg'];
echo $data['receiverId'];
// Save User ID receiver Id message in table
//Now Get Chat Connection by user id By Table Which is saved onOpen function
$UptGetChatConnection = $objChatroom->getChatConnectionUserID($data['receiverId']);
$ReciId = $UptGetChatConnection[0]['ConnectionID'];
foreach ($this->clients as $client) {
if ($from == $client) {
$data['from'] = "Me";
$data['fromname'] = 5;
} else {
$data['from'] = $user['name'];
$data['fromname'] = 6;
}
echo $client->resourceId;
echo $ReciId."\n";
if($client->resourceId == $ReciId || $from == $client){
$client->send(json_encode($data));
}
}
}
now the message has been sending by connection ID not broadcast only send particular
users
// SOCKET VARIABLE DECLARATION
var mySocket;
// MESSAGE
const socketMessageListener = function(event) {
var data = JSON.parse(event.data);
};
// OPEN SOCKET
const socketOpenListener = function(event) {
console.log('Connected');
};
// CLOSE OR DISCONNECT SOCKET
const socketCloseListener = function(event) {
if (mySocket) {
console.error('Disconnected.');
}
mySocket = new WebSocket('ws://localhost:8080');
mySocket.addEventListener('open', socketOpenListener);
mySocket.addEventListener('message', socketMessageListener);
mySocket.addEventListener('close', socketCloseListener);
};
// CALL THE LISTENER
socketCloseListener();
function sendMessage(data) {
mySocket.send(JSON.stringify(data));
}
user3049006 i found this code in my research to keep the connection persistent if fail retry the connection again.
Related
I have a php mvc website that contain controller - model - view
How I can call a function in flutter.
fo example the login form of the website has:
controller function:
function checkuser()
{
$form= $_POST;
$error=$this->model->checkUser($form);
Model::sessionInit();
$userId=Model::sessionGet('userId');
if ($userId==false )
{
// header('location:'.URL.'login');
echo '<script>console.log("invalid username or password")</script>';
}
else
{
// header('location:'.URL.'userpanel');
echo '<script>console.log("successfull... you are in.")</script>';
}
if ($error !='')
{
echo '<script>console.log("there is some error")</script>';
// $data=['error'=>$error];
// $this->view('login/index',$data);
}
}
and the model is:
function checkUser($form)
{
#$email=$form['username'];
#$password=$form['password'];
#$remember=$form['remember'];
$error='';
$sql= "select * from tbl_users WHERE email=? and password=?";
$params=[$email,$password];
$result=$this->doSelect($sql,$params,1);
if (sizeof($result)>0 and !empty($email) and !empty($password))
{
Model::sessionInit();
Model::sessionSet('userId',$result['id']);
if ($remember=='on')
{
$expiry = time() + (86400 * 7);
setcookie('userIdc',$result['id'],$expiry,'/');
}
}
else {
$error='not found user.';
}
return $error;
}
the link of the controller will be /login/checkuser
I try to send request from flutter with http package but can not give a good result.
please help me about the structure of the code in flutter and how to send the data username and password to server and give feedback from there.
thanks so much.
best regards
try something like below
import 'package:http/http.dart' as http;
import 'dart:convert';
userLogin() async {
Map<String, String?> userData = {
'username': 'username',
'password': 'password'
};
http.Response response = await http.post(
Uri.parse('yourUrl'),
headers: {'youHeader'},
body: json.encode(userData),
);
String jsonStr = (response.body);
try {
final mapData = jsonDecode(jsonStr);
if (mapData is Map) {
// var loginSuccess = await(AfterLogin.updatesAfterLogin(mapData)); //do other stuffs
} else {
return false;
}
} catch (e, trace) {
debugPrint("error $e, ${trace.toString()}");
return false;
}
}
When i run gearman upto three workers on single server is working fine but when i start the 4th one php code is working fine but can't detect the new worker and also not clearing the job queue.
protected function createWorker()
{
$this->worker = new \GearmanWorker();
$config = $this->app->config->job_remote;
$this->worker->addServer($config['host'], $config['port']);
return $this->worker;
}
public function listen($eventType, $callback)
{
if (!($this->worker instanceof \GearmanWorker)){
$this->worker = $this->createWorker();
}
$this->worker->addFunction($eventType, $callback);
return $this->worker;
}
public function doWork($worker)
{
if (!($worker instanceof \GearmanWorker)){
$worker = $this->createWorker();
}
$this->worker = $worker;
while (1) {
$this->worker->work();
$this->app->log->debug($this->worker->returnCode());
if ($this->worker->returnCode() != \GEARMAN_SUCCESS) {
break;
}
}
}
First I am calling 'listen' method and then 'doWork' method
Client Side Code:
protected function createClient()
{
$this->client = new \GearmanClient();
$config = $this->app->config->job_remote;
$this->client->addServer($config['host'], $config['port']);
return $this->client;
}
public function addTask($eventType, array $params)
{
if (!($this->client instanceof \GearmanClient)){
$this->client = $this->createClient();
}
// add single task in queue
$this->client->addTaskBackground($eventType, serialize($params));
// Run task
$this->client->runTasks();
}
Using GearmanManager You can run upto any no. of workers untill it slows down your computer and uses 100% cpu and 100% memory.
Gearman.ini
[GearmanManager]
worker_dir=./workers
count=50
dedicated_count=1
max_worker_lifetime=3600
auto_update=1
log_file=./logs/WebAnalyzerWorker.log
max_runs_per_worker=3
timeout=600
Run Gearman worker
./vendor/brianlmoon/gearmanmanager/pecl-manager.php -c ./gearman.ini -vvvvv
Worker CLass
<?php
include dirname(__DIR__)."/vendor/autoload.php";
use Services\Log;
use Services\ServiceInitialization;
use Lib\Technology\FindWhoisRecords;
use Illuminate\Database\Capsule\Manager as Capsule;
class DomainDetailFetchJob{
public function beforeRun()
{
ServiceInitialization::loadConfig();
ServiceInitialization::loadDatabaseConfiguration();
}
public function run($job, &$log)
{
$log[] = "Starting whois job";
$collection = "whois";
$this->beforeRun();
ServiceInitialization::$config->all();
$workload = $job->workload();
//workload
$whois = new FindWhoisRecords($workload);
if($whois->whois_result["err_code"]==0) {
$log[] = "Whois Info fetch successful";
//success save the details
$whois->whois_result["result"]["workload"] = $workload;
Capsule::table($collection)->where("workload", $workload)->update($whois->whois_result["result"], ['upsert' => true]);
}
else {
$log[] = "Whois Info fetch failed";
$logger = new Log();
$logger->write($whois->whois_result["err_msg"], "error", "Whois Record Job");
unset($logger);
}
}
}
Gearman Client
$client = new GearmanClient();
$client->addServer();
$client->doBackground("DomainDetailFetchJob", $url);
I have to subscribe to all the channels of my Redis db and simultaneously read data from another hash in the same db node. The following is the code I have written for this using phpredis:
$notif = new Worker;
try {
// connect redis
$notif->connectRedisServer();
// start the worker processing
$notif->startWorkerProcess();
}
catch(Exception $e) {
exit('Exception: '.$e->getMessage());
}
class Worker {
private $redis;
public $recentHash = 'tracker.data.recent';
public $notifHash = 'tracker.settings.data';
public $serverConnectionDetails = { //Redis server connection details
};
private $redis;
// redis database used for getting recent record of every imei
const RECENTDATA_DB = 1;
public function connectRedisServer() {
if(!empty($this->redis))
return $this->redis; // already connected
$this->redis = new Redis();
if(!$this->redis->pconnect(
$this->serverConnectionDetails['redis']['host'],
$this->serverConnectionDetails['redis']['port']
))
return 'Redis connection failed.';
if(isset($this->serverConnectionDetails['redis']['password'])
&&
!$this->redis->auth($this->serverConnectionDetails['redis']['password'])
)
return 'Redis authentication failed.';
return $this->redis;
}
public function startWorkerProcess() {
$this->redis->select(self::RECENTDATA_DB);
$this->redis->pSubscribe(array('*'), array($this, 'processRedisData'));
}
public function processRedisData($redis, $pattern, $chan, $msg) {
$message = rtrim((string) $msg,",");
$tData = json_decode($message, true);
$tId = (int) $tData['tID'];
echo "Recent Published Data:";
var_dump($tData);
$data = $this->getNotifSettings($tId);
if(!empty($data)) {
echo "Redis Settings: ";
var_dump($trackerSettings);
}
}
public function getNotifSettings($tId) {
$data = $this->redis->hGet($this->notifHash, $tId); //This command doesn't return any data even if it exists for the $tId
if($data === false)
return null;
$data = json_decode($data, true);
return $data; // Always comes up as null
}
}
The problem here is that once I get subscribed to all the channels on db1 in Redis. I don't get any result if I try to run HGET, even though the data for the given key exists in the db. I have put additional comments in the code above to explain where the problem is. Check getNotifSettings() function.
Any help will be appreciated.
I am making an html5 game www.titansoftime.com
I am using ratchet as a php websocket server solution. It works great! http://socketo.me/docs/push
I have done several standalone test using the php pthreads extension and have seen some very exciting results. It truly works and works well.. as long as websockets aren't in the mix.
Pthreads give php multithreading capabilities (it really does work and it's amazing). http://php.net/manual/en/book.pthreads.php
This is what I do:
/src/server.php
This is the file that launches the daemon.
<?php
session_start();
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Pusher;
require __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../mysql.cls.php';
require_once __DIR__ . '/../game.cls.php';
require_once __DIR__ . '/../model.cls.php';
$mysql = new mysql;
$game = new game;
$loop = React\EventLoop\Factory::create();
$pusher = new MyApp\Pusher();
$loop->addPeriodicTimer(0.50, function() use($pusher){
$pusher->load();
});
$webSock = new React\Socket\Server($loop);
if ($loop instanceof \React\EventLoop\LibEventLoop) {
echo "\n HAS LibEvent";
}
$webSock->listen(8080, '0.0.0.0'); // Binding to 0.0.0.0 means remotes can connect
$webServer = new Ratchet\Server\IoServer(
new Ratchet\Http\HttpServer(
new Ratchet\WebSocket\WsServer($pusher)
),
$webSock
);
$loop->run();
This all works fine.
/src/MyApp/Pusher.php
This class pushes data to all connected users.
<?php
namespace MyApp;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
class AsyncThread extends \Thread{
public $client;
public function __construct($client){
$this->client = $client;
}
public function run(){
// do work on $this->client
$user = mysql::assoc('SELECT * from users WHERE connection_id = "'.$this->client->resourceId.'"');
// etc..
$this->client->send(json_encode(array('foo'=>'bar')));
}
}
class Pusher implements MessageComponentInterface{
public static $clients = array();
#load
public static function load(){
$client_count = count(self::$clients);
echo "\n\n\n".'Serving to '.$client_count.' clients. '.time();
$start = $istart = microtime(true);
if( !count(self::$clients) ){
if( !mysql_ping() ){
$game->connect();
}
}
$threads = array();
foreach( self::$clients as $key => $client ){
// HANDLE CLIENT
// This works just fine, the only problem is that if I have lets say 50 simultaneous users, the people near the end of the clients array will have to wait till the other users have been processed. This is not desirable
$client->send(json_encode('foo'=>'bar'));
// So I tried this:
$threads[$key] = new AsyncThread($client);
$threads[$key]->start();
// At this point the AsyncThread class will throw a fatal error complaining about not being able to serialize a closure.
// If I dont set "$this->data = $client;" in the thread constructor no error appears but now I cant use the data.
// Also regardless of whether or not I bind the data in the AsyncThread constructor,
// the connection disappears if I call "new AsyncThread($client)". I cannot explain this behavior.
}
}
public function onMessage(ConnectionInterface $from, $msg) {
global $game;
if( $msg ){
$data = json_decode($msg);
if( $data ){
switch( $data->task ){
#connect
case 'connect':
echo "\n".'New connection! ('.$from->resourceId.') '.$from->remoteAddress;
self::$clients[] = $from;
break;
default:
self::closeConnection($from);
echo "\nNO TASK CLOSING";
break;
}
}else{
echo "\n NO DATA";
self::closeConnection($from);
}
}else{
echo "\n NO MSG";
self::closeConnection($from);
}
}
public function closeConnection($conn){
global $game;
if( $conn ){
if( $conn->resourceId ){
$connid = $conn->resourceId;
$conn->close();
$new = array();
foreach( self::$clients as $client ){
if( $client->resourceId != $connid ){
$new[] = $client;
}
}
self::$clients = $new;
$game->query('UPDATE users set connection_id = 0 WHERE connection_id = "'.intval($connid).'" LIMIT 1');
echo "\n".'Connection '.$connid.' has disconnected';
}
}
}
public function onClose(ConnectionInterface $conn) {
echo "\nCLIENT DROPPED";
self::closeConnection($conn);
}
public function onOpen(ConnectionInterface $conn) {
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "\nCLIENT ERRORED";
self::closeConnection($conn);
}
public function onSubscribe(ConnectionInterface $conn, $topic) {
}
public function onUnSubscribe(ConnectionInterface $conn, $topic) {
}
public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {
}
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) {
}
}
This all works fine as long as I don't create a thread inside the event loop.
Am I going about this the wrong way or is php multithreading and websockets incompatible?
check this package https://github.com/huyanping/react-multi-process
Install
composer require jenner/react-multi-process
How to use it?
So simple like:
$loop = React\EventLoop\Factory::create();
$server = stream_socket_server('tcp://127.0.0.1:4020');
stream_set_blocking($server, 0);
$loop->addReadStream($server, function ($server) use ($loop) {
$conn = stream_socket_accept($server);
$data = "pid:" . getmypid() . PHP_EOL;
$loop->addWriteStream($conn, function ($conn) use (&$data, $loop) {
$written = fwrite($conn, $data);
if ($written === strlen($data)) {
fclose($conn);
$loop->removeStream($conn);
} else {
$data = substr($data, 0, $written);
}
});
});
// the second param is the sub process count
$master = new \React\Multi\Master($loop, 20);
$master->start();
An example using jenner/simple_fork like:
class IoServer {
/**
* #param int $count worker process count
* Run the application by entering the event loop
* #throws \RuntimeException If a loop was not previously specified
*/
public function run($count = 1) {
if (null === $this->loop) {
throw new \RuntimeException("A React Loop was not provided during instantiation");
}
if($count <= 1){
$this->loop->run();
}else{
$loop = $this->loop;
$master = new \Jenner\SimpleFork\FixedPool(function() use($loop) {
$this->loop->run();
}, $count);
$master->start();
$master->keep(true);
// or just
// $master = new \React\Multi\Master($this->loop, $count);
// $master->start();
}
}
}
I have library for xmpp transactions used jaxl libraries:
class xmpp{
public function register_user($username, $password){
require_once 'JAXL/jaxl.php';
$this->client = new JAXL(array(
'jid' => 'localhost',
'log_level' => JAXL_ERROR
));
$this->username = $username;
$this->password = $password;
$this->client->require_xep(array(
'0077' // InBand Registration
));
$thisClassObject =& $this;
$this->client->add_cb('on_stream_features', function($stanza) use(&$thisClassObject) {
$thisClassObject->client->xeps['0077']->get_form('localhost');
return array($thisClassObject, 'wait_for_register_form');
});
$this->client->start();
return;
}
public function wait_for_register_response($event, $args) {
if($event == 'end_stream') {
return;
}
else if($event == 'stanza_cb') {
$stanza = $args[0];
if($stanza->name == 'iq') {
if($stanza->attrs['type'] == 'result') {
echo "registration successful".PHP_EOL."shutting down...".PHP_EOL;
$this->client->end_stream();
return 'logged_out';
}
else if($stanza->attrs['type'] == 'error') {
$error = $stanza->exists('error');
echo "registration failed with error code: ".$error->attrs['code']." and type: ".$error->attrs['type'].PHP_EOL;
echo "error text: ".$error->exists('text')->text.PHP_EOL;
echo "shutting down...".PHP_EOL;
$this->client->end_stream();
return "logged_out";
}
}
}
}
public function wait_for_register_form($event, $args) {
$stanza = $args[0];
$query = $stanza->exists('query', NS_INBAND_REGISTER);
if($query) {
$form = array();
$instructions = $query->exists('instructions');
if($instructions) {
echo $instructions->text.PHP_EOL;
}
$this->client->xeps['0077']->set_form($stanza->attrs['from'], array('username' => $this->username, 'password' => $this->password));
return array($this, "wait_for_register_response");
}
else {
$this->client->end_stream();
return "logged_out";
}
}
}
these code are same as register_user.php, but implemented in a class;
i use this class in my code in this way:
$xmppObj = new xmpp();
$xmppObj('user','password');
/*
some more code after this
/*
when it execute , create user successfully but it's print a message ('registration successful ...') and application exited and it doesn't execute "some code after this" after the class function, in the other word it doesn't follow the code...
What can I do for solve this problem, a person can help me that familiar with JAXL library.
Looks like you are pretty much using the same code as found inside examples/register_user.php. Once user registration is successful, script closes XMPPStream as evident from this section of the code:
if($stanza->attrs['type'] == 'result') {
echo "registration successful".PHP_EOL."shutting down...".PHP_EOL;
$this->client->end_stream();
return 'logged_out';
}
You MUST instead call $client->send_end_stream(); and not $client->end_stream();. This will make sure underlying XMPPStream makes proper FSM state transition. Also add a callback for on_disconnect event, inside this callback you can again try to connect back with newly registered XMPP account and it should just work fine.
Note: Kindly checkout latest code from the repository. I made some updates which will allow core JAXLLoop to be re-initialized. If you are interested in details, here is the commit log.