Task progress from a ZeroMQ worker - php

Fairly new to ZeroMQ. I have a simple REQ/REP queue like below. I am using PHP but that doesn't matter as any language binding would be fine for me.
This is client to request a task
$ctx = new ZMQContext();
$req = new ZMQSocket($ctx, ZMQ::SOCKET_REQ);
$req->connect('tcp://localhost:5454');
$req->send("Export Data as Zip");
echo $i . ":" . $req->recv().PHP_EOL;
And this is a worker to actually perform the task.
$ctx = new ZMQContext();
$srvr = new ZMQSocket($ctx, ZMQ::SOCKET_REP);
$srvr->bind("tcp://*:5454");
echo "Server is started at port $port" . PHP_EOL;
while(true)
{
$msg = $srvr->recv();
echo "Message = " . $msg . PHP_EOL;
// Do the work here, takes 10 min, knows the count of lines added and remaining
$srvr->send($msg . " is exported as zip file" . date('H:i:s'));
}
As the task of exporting data takes about 10 min, I want to connect to the server from a different client and get the progress/ percentage of the task done.
I am wondering if that's even a valid approach.
I tried this approach where REQ/REP part works but I get nothing in PUB/SUB part
Server part
$ctx = new ZMQContext();
$srvr = new ZMQSocket($ctx, ZMQ::SOCKET_REP);
$srvr->bind("tcp://*:5454");
// add PUB socket to publish progress
$c = new ZMQContext();
$p = new ZMQSocket($c, ZMQ::SOCKET_PUB);
$p->bind("tcp://*:5460");
echo "Server is started at port 5454" . PHP_EOL;
$prog = 0;
while(true)
{
$p->send($prog++ . '%'); // this part doesn't get to the progress client
$msg = $srvr->recv();
echo "Message = " . $msg . PHP_EOL;
sleep(2);// some long task
$srvr->send($msg . " Done zipping " . date('H:i:s'));
}
Progress client
$ctx = new ZMQContext();
$stat = new ZMQSocket($ctx, ZMQ::SOCKET_SUB);
$stat->connect('tcp://localhost:5460');
while (true){
echo $stat->recv() . PHP_EOL; //nothing shows here
}
Request client
$ctx = new ZMQContext();
$req = new ZMQSocket($ctx, ZMQ::SOCKET_REQ);
$req->connect('tcp://localhost:5454');
for($i=0;$i<100;$i++){
$req->send("$i : Zip the file please");
echo $i . ":" . $req->recv().PHP_EOL; //works and get the output
}

The concept is feasible, some tuning needed:
All PUB counterparties have to setup any non-default subscription, via, at least an empty subscription .setsockopt( ZMQ_SUBSCRIBE, "" ) meaning receive all TOPICs ( none "filter"-ed out ).
Next, both PUB-side and SUB sides ought get .setsockopt( ZMQ_CONFLATE, 1 ) configured, as there is of no value to populate and feed all intermediate values into the en-queue/de-queue pipeline, once the only value is in the "last", most recent message.
Always, the non-blocking mode of the ZeroMQ calls ought be preferred ( .recv( ..., flags = ZMQ_NOBLOCK ) et al ) or the Poller.poll() pre-tests ought be used to sniff first for a (non)-presence of a message, before spending more efforts on reading its context "from" ZeroMQ context-manager. Simply put, there are not many cases, where blocking-mode service calls may serve well in a production-grade system.
Also some further tweaking may help the PUB side, in case a more massive "attack" comes from the un-restricted pool of SUB-side entities and PUB has to create / manage / maintain resources for each of these ( unrestricted ) counterparties.

You need only use PUB/SUB if there is more than one client wanting to receive the same progress updates. Just use PUSH/PULL for a simple, point to point transfer that works over tcp.
Philosophical Discussion
With problems such as this to solve there's two approaches.
Use additional sockets to convey additional message types,
Use just two sockets, but convey more than one message type through them
You're talking about doing 1). It might be worth contemplating 2), though I must emphasise that I know next to nothing of PHP and so don't know if there are language features that encourage one to have separate request and progress clients.
If you do, your original client needs a loop (after it has sent the request) to receive multiple messages, either progress update messages or the final result. Your server, whilst it is doing its 10 minute lookup, will send regular progress update messages, and the final result message at the end. You would probably use PUSH/PULL client to server, and the same again for the progress / result from the server back to the client.
It is architecturally more flexible to follow 2). Once you have a means of sending two or more message types through a single socket and of decoding them at the receiving end, you can send more. For example, you could decide to add a 'cancel' message from the client to the server, or a partial results message from the server back to the client. This is much easier to extend than to keep adding more sockets to your architecture simply because you want to add another message flow between the client and server. Again, I don't know enough about PHP to say that this would definitely be the right way of doing it in that language. It certainly makes a lot of sense in C, C++.
I find things like Google Protocol Buffers (I prefer ASN.1) very useful for this kind of thing. These allow you to define the types of messages you want to send, and (at least with GPB), combine them together inside a single 'oneof' (in ASN.1 one uses tagging to tell different messages apart). GPB and ASN.1 are handy because then you can use different languages, OSes and platforms in your system without really having to worry about what it is being sent. And being binary (not text) they're more efficient across network connections.

Related

iOS application push notifications

We have an application built in PHP that sends out push notifications to both Android and iOS. Our problem is there are some device ids for iOS that seem to completely halt the sending of the all other iOS push notifications in our script, they even say that they have been sent without errors but it stops all notifications in the loop afterwards.
If we then remove the offending device id from our database the sending script works for all devices after the offending device. It is very strange and cant seem to figure out why.
Does anyone have any experience with this? Is it to do with sending to a device ID that doesnt exist anymore will stop apple from completing our script on that specific connection?
Here is our sending script in PHP (this works for all devices apart from the odd rogue device id):
$tHost = 'gateway.push.apple.com';
$tPort = 2195;
$tCert = "path_to_our_cert.pem";
$tPassphrase = 'passphrase';
$tAlert = $this->title; //assign a title
$tBadge = 8;
$tSound = 'default';
$tPayload = $this->body_plain; //assign a body
// Create the message content that is to be sent to the device.
$tBody['aps'] = array ('alert' => $tAlert,'badge' => $tBadge,'sound' => $tSound,);
$tBody ['payload'] = $tPayload;
$tBody = json_encode ($tBody);
$tContext = stream_context_create ();
stream_context_set_option ($tContext, 'ssl', 'local_cert', $tCert);
stream_context_set_option ($tContext, 'ssl', 'passphrase', $tPassphrase);
$tSocket = stream_socket_client ('ssl://'.$tHost.':'.$tPort, $error, $errstr, 30, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $tContext);
if (!$tSocket) exit ("APNS Connection Failed: $error $errstr" . PHP_EOL);
//Loop through all devices to send
foreach($this->device->devices as $item){
if($item->os != "iOS") continue;
if(session::getAdministratorStaffSchoolID() != $item->school_id) continue;
$tToken = $item->device_id;//assign the device id
$tMsg = chr (0) . chr (0) . chr (32) . pack ('H*', $tToken) . pack ('n', strlen ($tBody)) . $tBody;
$tResult = fwrite ($tSocket, $tMsg, strlen ($tMsg));
}
fclose ($tSocket);
Does anyone have any ideas regarding this?
Many thanks
So, just a thought, but you're using the legacy format to send the notification:
$tMsg = chr (0) . chr (0) . chr (32) . pack ('H*', $tToken) . pack ('n', strlen ($tBody)) . $tBody;
And:
Error response. With the legacy format, if you send a notification
packet that is malformed in some way—for example, the payload exceeds
the stipulated limit—APNs responds by severing the connection. It
gives no indication why it rejected the notification. The enhanced
format lets a provider tag a notification with an arbitrary
identifier. If there is an error, APNs returns a packet that
associates an error code with the identifier. This response enables
the provider to locate and correct the malformed notification.
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Appendixes/LegacyFormat.html
So maybe APNS is just dropping the connection on you? That's why all the remaining notifications actually don't get through. Take a closer look at those payloads. And might be a good time to move to the new format.
AFAIK APNs will close the connection as soon as any error occurs (invalid device token, malformed payload, etc.). Your code that sends out Push needs to be prepared to recover from this situation, reconnect and then restart the work.
Take a look at the 3rdParty libraries out there to execute this job for you. You'll rely on the community work.
I can referral Pushy which is written in Java. I have used it in 2 different projects and I can say it works fine and it's an very active project (a lot of discussions and updates there).
I don't know any of them using PHP, but probably there is one

PHP Azure SDK Service Bus returns Malformed Response

I'm working on trace logger of sorts that pushes log message requests onto a Queue on a Service Bus, to later be picked off by a worker role which would insert them into the table store. While running on my machine, this works just fine (since I'm the only one using it), but once I put it up on a server to test, it produced the following error:
HTTP_Request2_MessageException: Malformed response: in D:\home\site\wwwroot\vendor\pear-pear.php.net\HTTP_Request2\HTTP\Request2\Adapter\Socket.php on line 1013
0 HTTP_Request2_Response->__construct('', true, Object(Net_URL2)) D:\home\site\wwwroot\vendor\pear-pear.php.net\HTTP_Request2\HTTP\Request2\Adapter\Socket.php:1013
1 HTTP_Request2_Adapter_Socket->readResponse() D:\home\site\wwwroot\vendor\pear-pear.php.net\HTTP_Request2\HTTP\Request2\Adapter\Socket.php:139
2 HTTP_Request2_Adapter_Socket->sendRequest(Object(HTTP_Request2)) D:\home\site\wwwroot\vendor\pear-pear.php.net\HTTP_Request2\HTTP\Request2.php:939
3 HTTP_Request2->send() D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\Common\Internal\Http\HttpClient.php:262
4 WindowsAzure\Common\Internal\Http\HttpClient->send(Array, Object(WindowsAzure\Common\Internal\Http\Url)) D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\Common\Internal\RestProxy.php:141
5 WindowsAzure\Common\Internal\RestProxy->sendContext(Object(WindowsAzure\Common\Internal\Http\HttpCallContext)) D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\Common\Internal\ServiceRestProxy.php:86
6 WindowsAzure\Common\Internal\ServiceRestProxy->sendContext(Object(WindowsAzure\Common\Internal\Http\HttpCallContext)) D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\ServiceBus\ServiceBusRestProxy.php:139
7 WindowsAzure\ServiceBus\ServiceBusRestProxy->sendMessage('<queuename>/mes…', Object(WindowsAzure\ServiceBus\Models\BrokeredMessage)) D:\home\site\wwwroot\vendor\microsoft\windowsazure\WindowsAzure\ServiceBus\ServiceBusRestProxy.php:155
⋮
I've seen previous posts that describe similar issues; Namely:
Windows Azure PHP Queue REST Proxy Limit (Stack Overflow)
Operations on HTTPS do not work correctly (GitHub)
That imply that this is a known issue regarding the PHP Azure Storage libraries, where there are a limited amount of HTTPS connections allowed. Before requirements were changed, I was accessing the table store directly, and ran into this same issue, and fixed it in the way the first link describes.
The problem is that the Service Bus endpoint in the connection string, unlike Table Store (etc.) connection string endpoints, MUST be 'HTTPS'. Trying to use it with 'HTTP' will return a 400 - Bad Request error.
I was wondering if anyone had any ideas on a potential workaround. Any advice would be greatly appreciated.
Thanks!
EDIT (After Gary Liu's Comment):
Here's the code I use to add items to the queue:
private function logToAzureSB($source, $msg, $severity, $machine)
{
// Gather all relevant information
$msgInfo = array(
"Severity" => $severity,
"Message" => $msg,
"Machine" => $machine,
"Source" => $source
);
// Encode it to a JSON string, and add it to a Brokered message.
$encoded = json_encode($msgInfo);
$message = new BrokeredMessage($encoded);
$message->setContentType("application/json");
// Attempt to push the message onto the Queue
try
{
$this->sbRestProxy->sendQueueMessage($this->azureQueueName, $message);
}
catch(ServiceException $e)
{
throw new \DatabaseException($e->getMessage, $e->getCode, $e->getPrevious);
}
}
Here, $this->sbRestProxy is a Service Bus REST Proxy, set up when the logging class initializes.
On the recieving end of things, here's the code on the Worker role side of this:
public override void Run()
{
// Initiates the message pump and callback is invoked for each message that is received, calling close on the client will stop the pump.
Client.OnMessage((receivedMessage) =>
{
try
{
// Pull the Message from the recieved object.
Stream stream = receivedMessage.GetBody<Stream>();
StreamReader reader = new StreamReader(stream);
string message = reader.ReadToEnd();
LoggingMessage mMsg = JsonConvert.DeserializeObject<LoggingMessage>(message);
// Create an entry with the information given.
LogEntry entry = new LogEntry(mMsg);
// Set the Logger to the appropriate table store, and insert the entry into the table.
Logger.InsertIntoLog(entry, mMsg.Service);
}
catch
{
// Handle any message processing specific exceptions here
}
});
CompletedEvent.WaitOne();
}
Where Logging Message is a simple object that basically contains the same fields as the Message Logged in PHP (Used for JSON Deserialization), LogEntry is a TableEntity which contains these fields as well, and Logger is an instance of a Table Store Logger, set up during the worker role's OnStart method.
This was a known issue with the Windows Azure PHP, which hasn't been looked at in a long time, nor has it been fixed. In the time between when I posted this and now, We ended up writing a separate API web service for logging, and had our PHP Code send JSON strings to it over cURL, which works well enough as a temporary work around. We're moving off of PHP now, so this wont be an issue for much longer anyways.

Quickbooks integration (intranet)

I need to integrate vTiger CRM with Quickbooks 2010. So it is PHP. QB server and Linux box running vTiger are sitting in the same office. I try to figure out if there is a way to make PHP talk directly to QB server without using web connector. We need real time data access. Not like real 'real time', but report needs to be generated in like 5-30 seconds. Only reading of data, no writing. Any way to send/receive qbXML directly to QB server?
Thanks in advance.
Michael
This can be done... but be aware that QuickBooks isn't always the most reliable piece of software to integrate with for real-time data access - connections to QuickBooks tend to be both slow and fragile, which is why you usually see a queued model with the Web Connector.
What you could do is write a custom C# component (look at the examples included with the SDK, specifically this one: C:\Program Files (x86)\Intuit\IDN\QBSDK12.0\samples\qbdt\c-sharp\qbXML\CustomerAdd) which opens a connection to QuickBooks, and then does whatever.
"Whatever" might be something like:
Run a query against QuickBooks, and then HTTP POST the data back to vTiger
or
Open a socket and listen for incoming requests, in which case PHP could connect to the socket and feed the component requests which it could relay to QuickBooks, and then return the response
Be aware that generating a report in QuickBooks every 5 seconds is probably going to slow QuickBooks down considerably. Depending on how much data you're talking and what filters, it might not even be possible to get QuickBooks to generate a report that quickly.
Likewise, be aware that certain things cause connections to QuickBooks to fail, so you'll have to handle those gracefully. Specifically:
QuickBooks automatic updates will lock out integrated apps
If you have too many users logged in at once you'll get locked out
If someone else goes into single-user mode you'll get locked out
etc.
Maybe if you can provide more detail about why you need such frequent access to reports, we can provide some additional information.
Your code would look something like this:
while (true)
{
string input = "your fancy qbXML request goes here";
RequestProcessor2 rp = null;
string ticket = null;
string response = null;
try
{
rp = new RequestProcessor2 ();
rp.OpenConnection("", "Your App Name");
ticket = rp.BeginSession("", QBFileMode.qbFileOpenDoNotCare );
response = rp.ProcessRequest(ticket, input);
}
catch( System.Runtime.InteropServices.COMException ex )
{
MessageBox.Show( "COM Error Description = " + ex.Message, "COM error" );
return;
}
finally
{
if( ticket != null )
{
rp.EndSession(ticket);
}
if( rp != null )
{
rp.CloseConnection();
}
};
// Do something with the response here
...
}

XMPPHP as live support chat

My idea is to integrate a live support chat on a website. The users text is send with xmpphp to my jabber client with the jabberbot sender id and if I answer, the jabber bot, takes my answer and transfers the text to the user.
There is only one problem. How do I separate different users or different chats? I don't want all users to see the answer, but the user who asks. Is there a kind of unique chat id or another possibility, that I might just missed?
User => Website => Chatbot => me
I want to answer and send it back to the user, but how can I find out the correct user from my answer?
Last time I have to solve this problem I used this architecture:
Entlarge image
The Webserver provides an JavaScript / jQuery or flash chat.
After chat is started, the client ask the server all 1 Second for new Messages.
Alternative for 1 Sec Polling
If that is to slow for you, have a look at websockets.
http://martinsikora.com/nodejs-and-websocket-simple-chat-tutorial
http://demo.cheyenne-server.org:8080/chat.html
But Websockets could no provided by php. There for you need to change php + apchache agaist node.js or java.
Plain HTTP PHP Methode
In PHP you will connect to the PsyBnc with is polling the messages from the supporter for you.
The PsyBnc is an IRC bot.
The reason why don't directly connect to XMPP or BitlBee is that those protocols don't like the flapping connect, disconnect from PHP. Because you can not keep the session alive, you need something that is made for often and short connects. This is the PsyBnc.
I would use something like this:
http://pear.php.net/package/Net_SmartIRC/download
<?php
session_start();
$message = $_GET['message'];
$client_name = $_GET['client_name'];
if (empty($_SESSION['chat_id'])) {
$_SESSION['chat_id'] = md5(time(). mt_rand(0, 999999));
}
if (empty($_SESSION['supporter'])) {
// how do you select the supporter?
// only choose a free?
// We send first message to all supporter and the first who grapped got the chat (where only 3 gues)
}
$irc_host = "127.0.0.1";
$irc_port = 6667; // Port of PsyBnc
$irc_password = "password_from_psy_bnc";
$irc_user = "username_from_psy_bnc";
include_once('Net/SmartIRC.php');
class message_reader
{
private $messages = array();
public function receive_messages(&$irc, &$data)
{
// result is send to #smartirc-test (we don't want to spam #test)
$this->messages[] = array(
'from' => $data->nick,
'message' => $data->message,
);
}
public function get_messages() {
return $this->messages;
}
}
$bot = &new message_reader();
$irc = &new Net_SmartIRC();
$irc->setDebug(SMARTIRC_DEBUG_ALL);
$irc->setUseSockets(TRUE);
$irc->registerActionhandler(SMARTIRC_TYPE_QUERY|SMARTIRC_TYPE_NOTICE, '^' . $_SESSION['chat_id'], $bot, 'receive_messages');
$irc->connect($irc_host, $irc_port);
$irc->login($_SESSION['chat_id'], $client_name, 0, $irc_user, $irc_password);
$irc->join(array('#bitlbee'));
$irc->listen();
$irc->disconnect();
// Send new Message to supporter
if (!empty($message)) {
$irc->message(SMARTIRC_TYPE_QUERY, $_SESSION['supporter'], $message);
}
echo json_encode(array('messages' => $bot->get_messages()));
Connect the support instant messanger to PHP
We have allready an IRC connection to the PsyBnc, now we need to send messages from IRC to ICQ, XMPP, GOOGLE TALK, MSN, YAHOO, AOI...
Here for is a nice solution named BitlBee.
BitlBee offers an IRC Server with can transfer message from and to nearly all instant messager protocols. By aliasing those accounts. For example you need for your system only 1 Server account at google talk, icq ... and at all your supporter to the buddylist of those accounts. Now BitleBee will provide your boddylist as an irc chat.
Your requirements are rather confusing. As Joshua said, you don't need a Jabber bot for this. All you need is a Jabber server - which you should already have. What you do is, you create a volatile user account sessionid#*yourdomain.com* whenever the chat feature is used and then you can just reply to any incoming message like normal while your website client can fetch the messages meant for it whenever.
Alternatively you could create one user account - qa#yourdomain.com - and use XMPP resource identifiers for the routing part. XMPP allows for something like qa#yourdomain.com/*sessionid* and you should be able to tell your XMPP library to only query a specific resource. Most XMPP client software will also reply to a specific resource by default and open a new conversation when applicable. This method is less "clean" than the first, but it would work somewhat better if you can't arbitrarily create user accounts for some reason.
I don't know what XMPP server you are using, but you could also try the Fastpath plugin and webchat for Openfire. Which is meant to provide a support team service over XMPP.
That being said, your question itself seems to imply nothing more than the standard chat feature of XMPP, which is between two users. It just means that the support person has a unique chat with each user asking a question. No other user will see that conversation.

PHP - echo do other client

I`m making an little php server that has 2 clients, on connects to the php (device a) and controls the other (device b).
device a makes a get request to the php. now i want to push that command to device b. is there any way to echo to device b? or to make an request to the device b?
(i only need to send one character to device b)
Pushing to device is possible but depends on your device.
A solution would be websockets, see the following links for further reading:
http://www.websocket.org/
http://code.google.com/p/phpwebsocket/
Another solution would be longpolling which is easy to implement in php:
http://en.wikipedia.org/wiki/Push_technology#Long_polling
Very simple implementation of longpolling on server-side:
$ts = time();
while(true) {
// if there's something new, send the response to the server
// if not, continue with the loop
if($response = getSuperAwesomeResponse()) {
print $response;
break;
}
// timeout after 60 seconds
if( ($ts + 60) > time()) {
break;
}
sleep(1);
}
On the client-side you just need to send some sort of ajax calls
No, unless device B is running a server of some kind (any software that accepts incoming connections really). If that's the case then you can easily make HTTP requests to the device (e.g. even with file_get_contents) or have your own custom connection protocol (with sockets). There are also other options that allow you the same functionality but work in slightly different ways.
If the device is not running any servers then the best you can do is poll the server continuously to see if there are any commands for it. This is easier to set up (the server is already there) but also not efficient.
Device B could open a client connection to the server and wait for incomming data. If data comes in, the client running on device B could echo it.
PHP offers access to network sockets, see http://www.php.net/manual/en/book.sockets.php
Some PHP example code making use of LibEvent and ZMQ, which allows a higher level of access to sockets and queues:
Event-driven Server:
<?php
// create base and event
$base = event_base_new();
$event = event_new();
// Allocate a new context
$context = new ZMQContext();
// Create sockets
$rep = $context->getSocket(ZMQ::SOCKET_REP);
// Connect the socket
$rep->bind("tcp://127.0.0.1:5555");
// Get the stream descriptor
$fd = $rep->getsockopt(ZMQ::SOCKOPT_FD);
// Define event callback function
$fnc = function ($fd, $events, $arg) {
static $msgs = 1;
echo "CALLBACK FIRED" . PHP_EOL;
if($arg[0]->getsockopt (ZMQ::SOCKOPT_EVENTS) & ZMQ::POLL_IN) {
echo "Got incoming data" . PHP_EOL;
var_dump ($arg[0]->recv());
$arg[0]->send("Got msg $msgs");
if($msgs++ >= 10) event_base_loopexit($arg[1]);
}
};
// set event flags
event_set($event, $fd, EV_READ | EV_PERSIST, $fnc, array($rep, $base));
// set event base
event_base_set($event, $base);
// enable event
event_add($event);
// start event loop
event_base_loop($base);
ZeroMQ Client:
<?php
// Create new queue object
$queue = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REQ, "MySock1");
$queue->connect("tcp://127.0.0.1:5555");
// Assign socket 1 to the queue, send and receive
var_dump($queue->send("hello there!")->recv());
Source, Talk, Video (~22:00).

Categories