function broker_task() { // Prepare our context and sockets $context = new ZMQContext(); $frontend = new ZMQSocket($context, ZMQ::SOCKET_ROUTER); $backend = new ZMQSocket($context, ZMQ::SOCKET_ROUTER); $frontend->bind("tcp://*:5555"); $backend->bind("tcp://*:5556"); // Initialize poll set $poll = new ZMQPoll(); $poll->add($frontend, ZMQ::POLL_IN); $poll->add($backend, ZMQ::POLL_IN); $read = $write = array(); while (true) { $events = $poll->poll($read, $write); foreach ($read as $socket) { $zmsg = new Zmsg($socket); $zmsg->recv(); if ($socket === $frontend) { $zmsg->push("W"); $zmsg->set_socket($backend)->send(); } elseif ($socket === $backend) { $zmsg->pop(); $zmsg->push("C"); $zmsg->set_socket($frontend)->send(); } } } }
private function sendCommand($command, $msg = null) { if (!$msg) { $msg = new Zmsg(); } $msg->push($command); $msg->push(Commands::W_WORKER); $msg->push(""); if ($this->verbose) { printf("I: sending `%s` to broker %s", $command, PHP_EOL); echo $msg->__toString(), PHP_EOL; } $msg->set_socket($this->socket)->send(); }
/** * Send request to broker * Takes ownership of request message and destroys it when sent. * * @param string $service * @param Zmsg $request */ public function send($service, Zmsg $request) { // Prefix request with protocol frames // Frame 0: empty (REQ emulation) // Frame 1: "MDPCxy" (six bytes, MDP/Client x.y) // Frame 2: Service name (printable string) $request->push($service); $request->push(MDPC_CLIENT); $request->push(""); if ($this->verbose) { printf("I: send request to '%s' service: %s", $service, PHP_EOL); echo $request->__toString(); } $request->set_socket($this->client)->send(); }
function server_task() { // Launch pool of worker threads, precise number is not critical for ($thread_nbr = 0; $thread_nbr < 5; $thread_nbr++) { $pid = pcntl_fork(); if ($pid == 0) { server_worker(); exit; } } $context = new ZMQContext(); // Frontend socket talks to clients over TCP $frontend = new ZMQSocket($context, ZMQ::SOCKET_ROUTER); $frontend->bind("tcp://*:5570"); // Backend socket talks to workers over ipc $backend = new ZMQSocket($context, ZMQ::SOCKET_DEALER); $backend->bind("ipc://backend"); // Connect backend to frontend via a queue device // We could do this: // $device = new ZMQDevice($frontend, $backend); // But doing it ourselves means we can debug this more easily $read = $write = array(); // Switch messages between frontend and backend while (true) { $poll = new ZMQPoll(); $poll->add($frontend, ZMQ::POLL_IN); $poll->add($backend, ZMQ::POLL_IN); $poll->poll($read, $write); foreach ($read as $socket) { $zmsg = new Zmsg($socket); $zmsg->recv(); if ($socket === $frontend) { //echo "Request from client:"; //echo $zmsg->__toString(); $zmsg->set_socket($backend)->send(); } else { if ($socket === $backend) { //echo "Request from worker:"; //echo $zmsg->__toString(); $zmsg->set_socket($frontend)->send(); } } } } }
// - Route any request locally if we can, else to cloud while ($local_capacity + $cloud_capacity) { $poll = new ZMQPoll(); $poll->add($localfe, ZMQ::POLL_IN); if ($local_capacity) { $poll->add($cloudfe, ZMQ::POLL_IN); } $reroutable = false; $events = $poll->poll($readable, $writeable, 0); if ($events > 0) { foreach ($readable as $socket) { $zmsg = new Zmsg($socket); $zmsg->recv(); if ($local_capacity) { $zmsg->wrap(array_shift($worker_queue), ""); $zmsg->set_socket($localbe)->send(); $local_capacity--; } else { // Route to random broker peer printf("I: route request %s to cloud...%s", $zmsg->body(), PHP_EOL); $zmsg->wrap($_SERVER['argv'][mt_rand(2, $_SERVER['argc'] - 1)]); $zmsg->set_socket($cloudbe)->send(); } } } else { break; // No work, go back to backends } } if ($local_capacity != $previous) { // Broadcast new capacity
$poll = new ZMQPoll(); $poll->add($backend, ZMQ::POLL_IN); // Poll frontend only if we have available workers if ($available_workers) { $poll->add($frontend, ZMQ::POLL_IN); } $events = $poll->poll($read, $write); foreach ($read as $socket) { $zmsg = new Zmsg($socket); $zmsg->recv(); // Handle worker activity on backend if ($socket === $backend) { // Use worker address for LRU routing assert($available_workers < MAX_WORKERS); array_push($worker_queue, $zmsg->unwrap()); $available_workers++; // Return reply to client if it's not a READY if ($zmsg->address() != "READY") { $zmsg->set_socket($frontend)->send(); } } elseif ($socket === $frontend) { // Now get next client request, route to next worker // REQ socket in worker needs an envelope delimiter // Dequeue and drop the next worker address $zmsg->wrap(array_shift($worker_queue), ""); $zmsg->set_socket($backend)->send(); $available_workers--; } } // We never exit the main loop }
/** * Process message sent to us by a worker * * @param string $sender * @param Zmsg $msg */ public function worker_process($sender, $msg) { $command = $msg->pop(); $worker_ready = isset($this->workers[$sender]); $worker = $this->worker_require($sender); if ($command == MDPW_READY) { if ($worker_ready) { $this->worker_delete($worker, true); // Not first command in session } else { if (strlen($sender) >= 4 && substr($sender, 0, 4) == 'mmi.') { $this->worker_delete($worker, true); } else { // Attach worker to service and mark as idle $service_frame = $msg->pop(); $worker->service = $this->service_require($service_frame); $worker->service->workers++; $this->worker_waiting($worker); } } } else { if ($command == MDPW_REPLY) { if ($worker_ready) { // Remove & save client return envelope and insert the // protocol header and service name, then rewrap envelope. $client = $msg->unwrap(); $msg->push($worker->service->name); $msg->push(MDPC_CLIENT); $msg->wrap($client, ""); $msg->set_socket($this->socket)->send(); $this->worker_waiting($worker); } else { $this->worker_delete($worker, true); } } else { if ($command == MDPW_HEARTBEAT) { if ($worker_ready) { $worker->expiry = microtime(true) + HEARTBEAT_EXPIRY / 1000; } else { $this->worker_delete($worker, true); } } else { if ($command == MDPW_DISCONNECT) { $this->worker_delete($worker, true); } else { echo "E: invalid input message", PHP_EOL, $msg->__toString(); } } } } }
function main() { for ($client_nbr = 0; $client_nbr < NBR_CLIENTS; $client_nbr++) { $pid = pcntl_fork(); if ($pid == 0) { client_thread(); return; } } for ($worker_nbr = 0; $worker_nbr < NBR_WORKERS; $worker_nbr++) { $pid = pcntl_fork(); if ($pid == 0) { worker_thread(); return; } } $context = new ZMQContext(); $frontend = new ZMQSocket($context, ZMQ::SOCKET_ROUTER); $backend = new ZMQSocket($context, ZMQ::SOCKET_ROUTER); $frontend->bind("ipc://frontend.ipc"); $backend->bind("ipc://backend.ipc"); // Logic of LRU loop // - Poll backend always, frontend only if 1+ worker ready // - If worker replies, queue worker as ready and forward reply // to client if necessary // - If client requests, pop next worker and send request to it // Queue of available workers $available_workers = 0; $worker_queue = array(); $writeable = $readable = array(); while ($client_nbr > 0) { $poll = new ZMQPoll(); // Poll front-end only if we have available workers if ($available_workers > 0) { $poll->add($frontend, ZMQ::POLL_IN); } // Always poll for worker activity on backend $poll->add($backend, ZMQ::POLL_IN); $events = $poll->poll($readable, $writeable); if ($events > 0) { foreach ($readable as $socket) { // Handle worker activity on backend if ($socket === $backend) { // Queue worker address for LRU routing $zmsg = new Zmsg($socket); $zmsg->recv(); assert($available_workers < NBR_WORKERS); $available_workers++; array_push($worker_queue, $zmsg->unwrap()); if ($zmsg->body() != "READY") { $zmsg->set_socket($frontend)->send(); // exit after all messages relayed $client_nbr--; } } else { if ($socket === $frontend) { $zmsg = new Zmsg($socket); $zmsg->recv(); $zmsg->wrap(array_shift($worker_queue), ""); $zmsg->set_socket($backend)->send(); $available_workers--; } } } } } // Clean up our worker processes foreach ($worker_queue as $worker) { $zmsg = new Zmsg($backend); $zmsg->body_set('END')->wrap($worker, "")->send(); } sleep(1); }
/** * Send request to broker and get reply by hook or crook * Takes ownership of request message and destroys it when sent. * Returns the reply message or NULL if there was no reply. * * @param string $service * @param Zmsg $request * @param string $client * @return Zmsg */ 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(MDPC_CLIENT); if ($this->verbose) { printf("I: send request to '%s' service:", $service); echo $request->__toString(); } $retries_left = $this->retries; $read = $write = array(); while ($retries_left) { $request->set_socket($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) { echo "I: received reply:", $request->__toString(), PHP_EOL; } // Don't try to handle errors, just assert noisily assert($request->parts() >= 3); $header = $request->pop(); assert($header == MDPC_CLIENT); $reply_service = $request->pop(); assert($reply_service == $service); return $request; // Success } elseif ($retries_left--) { if ($this->verbose) { echo "W: no reply, reconnecting...", PHP_EOL; } // Reconnect, and resend message $this->connect_to_broker(); $request->send(); } else { echo "W: permanent error, abandoning request", PHP_EOL; break; // Give up } } }
/** * Initialise the master server that dispatches job to the worker processes * * @param int $port */ protected function initAsMaster($port) { $ctx = new ZMQContext(); $client = $ctx->getSocket(ZMQ::SOCKET_XREP); $client->bind('tcp://*:' . $port); $workers = $ctx->getSocket(ZMQ::SOCKET_XREQ); $workers->bind('ipc://' . $this->workersIpcName); $this->log(__CLASS__, "Master listening on port {$port}"); $readable = $writable = array(); while (true) { $poll = new ZMQPoll(); $poll->add($client, ZMQ::POLL_IN); $poll->add($workers, ZMQ::POLL_IN); $poll->poll($readable, $writable); foreach ($readable as $socket) { $zmsg = new Zmsg($socket); $zmsg->recv(); if ($socket === $client) { $this->log(__CLASS__, "Master sending job to worker"); $zmsg->set_socket($workers)->send(); } else { if ($socket === $workers) { $this->log(__CLASS__, "Master sending reply to client"); $zmsg->set_socket($client)->send(); } } } } }