public function testComplex() { $context = new \ZMQContext(); $output = new \ZMQSocket($context, \ZMQ::SOCKET_DEALER); $output->bind("inproc://zmsg_selftest"); $input = new \ZMQSocket($context, \ZMQ::SOCKET_ROUTER); $input->connect("inproc://zmsg_selftest"); // Test send and receive of single-part message $zmsgo = new Zmsg($output); $zmsgo->setLast("Hello"); $this->assertTrue($zmsgo->getLast() == "Hello"); $zmsgo->send(); $zmsgi = new Zmsg($input); $zmsgi->recv(); $this->assertTrue($zmsgi->parts() == 2); $this->assertTrue($zmsgi->getLast() == "Hello"); // Test send and receive of multi-part message $zmsgo = new Zmsg($output); $zmsgo->setLast("Hello"); $zmsgo->wrap("address1", ""); $zmsgo->wrap("address2"); $this->assertTrue($zmsgo->parts() == 4); $zmsgo->send(); $zmsgi = new Zmsg($input); $zmsgi->recv(); $this->assertTrue($zmsgi->parts() == 5); $zmsgi->unwrap(); $this->assertTrue($zmsgi->unwrap() == "address2"); $zmsgi->setLast(sprintf("%s%s", 'W', "orld")); $this->assertTrue($zmsgi->getLast() == "World"); // Pull off address 1, check that empty part was dropped $zmsgi->unwrap(); $this->assertTrue($zmsgi->parts() == 1); // Check that message body was correctly modified $part = $zmsgi->pop(); $this->assertTrue($part == "World"); $this->assertTrue($zmsgi->parts() == 0); // Test load and save $zmsg = new Zmsg(); $zmsg->setLast("Hello"); $zmsg->wrap("address1", ""); $zmsg->wrap("address2"); $this->assertTrue($zmsg->parts() == 4); $fh = fopen(sys_get_temp_dir() . "/zmsgtest.zmsg", 'w'); $zmsg->save($fh); fclose($fh); $fh = fopen(sys_get_temp_dir() . "/zmsgtest.zmsg", 'r'); $zmsg2 = new Zmsg(); $zmsg2->load($fh); assert($zmsg2->getLast() == $zmsg->getLast()); fclose($fh); $this->assertTrue($zmsg2->parts() == 4); }
/** * Send request to broker and get reply by hook or crook. * * Takes ownership of request message and destroys it when sent. * * * @param string $service The name of the service * @param Zmsg $request Request message * @return Zmsg Returns the reply message or NULL if there was no reply. */ public function send($service, Zmsg $request) { // Prefix request with protocol frames // Frame 1: "MDPCxy" (six bytes, MDP/Client) // Frame 2: Service name (printable string) $request->push($service); $request->push(Mdp::CLIENT); if ($this->verbose) { $this->log("ZMQDEBUG", "send request to '%s' service:\n--\n%s", $service, (string) $request); } $retries_left = $this->retries; $read = $write = array(); while ($retries_left) { $request->setSocket($this->client)->send(); // Poll socket for a reply, with timeout $poll = new ZMQPoll(); $poll->add($this->client, ZMQ::POLL_IN); $events = $poll->poll($read, $write, $this->timeout); // If we got a reply, process it if ($events) { $request->recv(); if ($this->verbose) { $this->log("ZMQDEBUG", "received reply:\n--\n%s", $request); } if ($request->parts() < 3) { throw new MdpException(sprintf("Expected more than 2 parts, but %d received", $request->parts())); } $header = $request->pop(); if ($header !== Mdp::CLIENT) { throw new MdpException(sprintf("Unexpected header %s, %s is expected", $header, Mdp::CLIENT)); } $replyService = $request->pop(); if ($replyService != $service) { throw new MdpException(sprintf("Unexpected service %s, %s is expected.", $replyService, $service)); } //Success return $request; } elseif ($retries_left--) { if ($this->verbose) { $this->log("WARN", "no reply, reconnecting..."); } // Reconnect $this->connect(); // Resend message again $request->send(); } else { if ($this->verbose) { $this->log("ERROR", "permanent error, abandoning request"); break; } } } }
/** * Receives a reply * * Returns the reply message or NULL if there was no reply. * Does not attempt to recover from a broker failure, this is not possible * without storing all unanswered requests and resending them all ... */ public function recv() { $read = $write = array(); // Poll socket for a reply, with timeout $poll = new ZMQPoll(); $poll->add($this->client, ZMQ::POLL_IN); $events = $poll->poll($read, $write, $this->timeout); // If we got a reply, process it if ($events) { $msg = new Zmsg($this->client); $msg->recv(); if ($this->verbose) { $this->log('ZMQDEBUG', "received reply:\n--\n%s", (string) $msg); } if ($msg->parts() < 4) { throw new MdpException("More than 3 parts are expected in the message."); } $msg->pop(); $header = $msg->pop(); if ($header !== Mdp::CLIENT) { throw new MdpException(sprintf("%s is expected.", Mdp::CLIENT)); } $repService = $msg->pop(); return $msg; } else { if ($this->verbose) { $this->log('WARN', "permanent error, abandoning request"); } return; } }
/** * Send reply, if any, to broker and wait for next request. * * @param Zmsg $reply optional Reply message object * @return Zmsg Returns if there is a request to process */ public function recv($reply = null) { // Format and send the reply if we were provided one if (!$reply && $this->expectReply) { throw new MdpException("Reply message is expected"); } if ($reply) { $reply->wrap($this->replyTo); $this->send(Mdp::WORKER_REPLY, null, $reply); } $this->expectReply = true; $read = $write = []; while (true) { $poll = new ZMQPoll(); $poll->add($this->worker, ZMQ::POLL_IN); $events = $poll->poll($read, $write, $this->heartbeat); if ($events) { $zmsg = new Zmsg($this->worker); $zmsg->recv(); if ($this->verbose) { $this->log("ZMQDEBUG", "received message from broker:\n--\n%s", (string) $zmsg); } $this->liveness = self::HEARTBEAT_LIVENESS; if ($zmsg->parts() < 3) { throw new MdpException(sprintf("Expected more then 2 parts, but %d received", $zmsg->parts())); } $zmsg->pop(); $header = $zmsg->pop(); if ($header !== Mdp::WORKER) { throw new MdpException(sprintf("Expected %s header, %s has been actually received", Mdp::WORKER, $header)); } $command = $zmsg->pop(); if ($command == Mdp::WORKER_REQUEST) { // We should pop and save as many addresses as there are // up to a null part, but for now, just save one… $this->replyTo = $zmsg->unwrap(); // We have a request to process return $zmsg; } elseif ($command == Mdp::WORKER_HEARTBEAT) { // Do nothing for heartbeats } elseif ($command == Mdp::WORKER_DISCONNECT) { $this->connect(); } else { if ($this->verbose) { $this->log("ERROR", "invalid input message\n--\n%s", (string) $zmsg); } } } elseif (--$this->liveness == 0) { // poll ended on timeout, $event being false if ($this->verbose) { $this->log("WARN", "disconnected from broker - retrying...\n"); } usleep($this->reconnect * 1000); $this->connect(); } // Send HEARTBEAT if it's time if (microtime(true) > $this->heartbeatAt) { $this->send(Mdp::WORKER_HEARTBEAT); $this->updateHeartbeatExpiry(); } } }
/** * This is the main listen and process loop */ public function listen() { $read = $write = array(); // Get and process messages forever or until interrupted while (true) { $poll = new ZMQPoll(); $poll->add($this->socket, ZMQ::POLL_IN); $events = $poll->poll($read, $write, $this->heartbeat); // Process next input message, if any if ($events) { $zmsg = new Zmsg($this->socket); $zmsg->recv(); if ($this->verbose) { $this->log("ZMQDEBUG", "received message:\n--\n%s", (string) $zmsg); } $sender = $zmsg->pop(); $empty = $zmsg->pop(); $header = $zmsg->pop(); if ($header == Mdp::CLIENT) { $this->processClient($sender, $zmsg); } elseif ($header == Mdp::WORKER) { $this->processWorker($sender, $zmsg); } else { if ($this->verbose) { $this->log("ERROR", "invalid message\n--\n%s", (string) $zmsg); } } } // Disconnect and delete any expired workers // Send heartbeats to idle workers if needed if (microtime(true) > $this->heartbeatAt) { $this->purgeWorkers(); foreach ($this->workers as $worker) { $this->workerSend($worker, Mdp::WORKER_HEARTBEAT); } $this->updateHeartbeatExpiry(); } } }