/** * Runs this notification server * * This notification server receives client connections and receives and * relays data from connected clients. * * @return void */ public function run() { $this->connect(); while (!$this->moribund) { $read = $this->getReadArray(); // Suppressing warnings for stream_select() because it will raise // a warning if interrupted by a signal. $result = @stream_select($read, $write = null, $except = null, null); if ($result === false || $result < 1) { continue; } // check for new connections if (in_array($this->socket->getRawSocket(), $read)) { try { $newSocket = new Net_Notifier_Socket_Accept($this->socket, null); } catch (Net_Notifier_Socket_Exception $e) { $this->log('Accepting client connection failed: reason: ' . $e->getMessage() . PHP_EOL, Net_Notifier_Logger::VERBOSITY_ERRORS); } $client = new Net_Notifier_WebSocket_Connection($newSocket); $this->clients[] = $client; $this->log('client connected from ' . $client->getIPAddress() . PHP_EOL, Net_Notifier_Logger::VERBOSITY_CLIENT); } // check for client data foreach ($this->getReadClients($read) as $client) { $moribund = false; // check if client closed connection $bytes = $client->getSocket()->peek(1); if (mb_strlen($bytes, '8bit') === 0) { $this->log(sprintf('client %s closed connection.' . PHP_EOL, $client->getIPAddress()), Net_Notifier_Logger::VERBOSITY_CLIENT); $moribund = true; } try { if ($client->read(self::READ_BUFFER_LENGTH)) { if ($client->getState() < Net_Notifier_WebSocket_Connection::STATE_CLOSING) { $receivedRelayMessage = false; $messages = $client->getTextMessages(); foreach ($messages as $message) { $this->log(sprintf('received message: "%s" from %s' . PHP_EOL, $message, $client->getIPAddress()), Net_Notifier_Logger::VERBOSITY_MESSAGES); $message = json_decode($message, true); if ($message === false || !isset($message['action'])) { $this->log('=> incorrectly formatted' . PHP_EOL, Net_Notifier_Logger::VERBOSITY_MESSAGES); $this->startCloseClient($client, Net_Notifier_WebSocket_Connection::CLOSE_PROTOCOL_ERROR, 'Incorrectly formatted message.'); break; } if ($message['action'] === 'shutdown') { $this->log(sprintf('shutting down at request of %s' . PHP_EOL, $client->getIPAddress()), Net_Notifier_Logger::VERBOSITY_MESSAGES); $this->startCloseClient($client, Net_Notifier_WebSocket_Connection::CLOSE_NORMAL, 'Received shutdown message.'); $this->moribund = true; } if ($message['action'] === 'listen') { $this->log(sprintf('set %s to listen' . PHP_EOL, $client->getIPAddress()), Net_Notifier_Logger::VERBOSITY_MESSAGES); if (!in_array($client, $this->listenClients)) { $this->listenClients[] = $client; } } else { $this->relayNotification($message); $receivedRelayMessage = true; } } if ($receivedRelayMessage) { $this->startCloseClient($client, Net_Notifier_WebSocket_Connection::CLOSE_NORMAL, 'Received message for relay.'); } } } else { $this->log(sprintf('got a message chunk from %s' . PHP_EOL, $client->getIPAddress()), Net_Notifier_Logger::VERBOSITY_CLIENT); if ($client->getState() === Net_Notifier_WebSocket_Connection::STATE_CLOSED) { $this->log(sprintf('completed close handshake from %s' . PHP_EOL, $client->getIPAddress()), Net_Notifier_Logger::VERBOSITY_CLIENT); $moribund = true; } } } catch (Net_Notifier_WebSocket_HandshakeFailureException $e) { $this->log(sprintf('failed client handshake: %s' . PHP_EOL, $e->getMessage()), Net_Notifier_Logger::VERBOSITY_CLIENT); } if ($moribund) { $this->closeClient($client); } } } $this->disconnect(); }
/** * Disconnects this client from the server * * @return void */ protected function disconnect() { // Initiate connection close. The WebSockets RFC recomends against // clients initiating the close handshake but we want to ensure the // connection is closed as soon as possible. $this->connection->startClose(Net_Notifier_WebSocket_Connection::CLOSE_GOING_AWAY, 'Client sent message.'); // read server close frame $state = $this->connection->getState(); $sec = intval($this->timeout / 1000); $usec = $this->timeout % 1000 * 1000; while ($state < Net_Notifier_WebSocket_Connection::STATE_CLOSED) { $read = array($this->socket->getRawSocket()); $result = stream_select($read, $write = null, $except = null, $sec, $usec); if ($result === 1) { $this->connection->read(self::READ_BUFFER_LENGTH); } else { // read timed out, just close the connection $this->connection->close(); } $state = $this->connection->getState(); } $this->connection = null; }