/** * @param resource $socket An open socket client resource */ public function __construct($socket) { \stream_set_blocking($socket, false); $this->state = $state = new \StdClass(); $readWatcherId = amp\onReadable($socket, static function ($wid, $socket) use($state) { $data = @\fread($socket, 8192); if ($data != "") { $state->bytesRead += \strlen($data); $op = \reset($state->readOperations); $op->buffer .= $data; Client::onRead($state); } else { Client::onEmptyRead($state); } }, $options = ["enable" => false]); $writeWatcherId = amp\onWritable($socket, static function ($wid, $socket) use($state) { $op = \current($state->writeOperations); if ($bytes = @\fwrite($socket, $op->buffer)) { $state->bytesSent += $bytes; Client::onWrite($state, $op, $bytes); } else { Client::onEmptyWrite($state); } }, $options = ["enable" => false]); $state->readWatcherId = $readWatcherId; $state->writeWatcherId = $writeWatcherId; $state->socket = $socket; $state->isDead = false; $state->localName = \stream_socket_get_name($socket, $wantPeer = false); $state->remoteName = \stream_socket_get_name($socket, $wantPeer = true); $state->readOperations = []; $state->writeOperations = []; $state->bytesRead = 0; $state->bytesSent = 0; // We avoid instantiating a closure every time a socket read/write completes // without exposing a method in the public API this way ... it may look hacky // but it's important for performance. if (empty(self::$succeeder)) { $callable = (new \ReflectionClass($this))->getMethod("succeed")->getClosure($this); self::$succeeder = $callable; } }
private function connect() { // If we're in the process of connecting already return that same promise if ($this->connectPromisor) { return $this->connectPromisor->promise(); } // If a read watcher exists we know we're already connected if ($this->readWatcher) { return new Success($this); } $this->connectPromisor = new Deferred(); $socketPromise = connect($this->uri, ["timeout" => $this->timeout]); $onWrite = function ($watcherId) { if ($this->outputBufferLength === 0) { disable($watcherId); return; } $bytes = @fwrite($this->socket, $this->outputBuffer); if ($bytes === 0) { $this->onError(new ConnectException("Connection went away (write)", $code = 1)); } else { $this->outputBuffer = (string) substr($this->outputBuffer, $bytes); $this->outputBufferLength -= $bytes; } }; $socketPromise->when(function ($error, $socket) use($onWrite) { $connectPromisor = $this->connectPromisor; $this->connectPromisor = null; if ($error) { $connectPromisor->fail(new ConnectException("Connection attempt failed", $code = 0, $error)); return; } $this->socket = $socket; foreach ($this->handlers["connect"] as $handler) { $pipelinedCommand = $handler(); if (!empty($pipelinedCommand)) { $this->outputBuffer = $pipelinedCommand . $this->outputBuffer; $this->outputBufferLength += strlen($pipelinedCommand); } } $this->readWatcher = onReadable($this->socket, function () { $read = fread($this->socket, 8192); if ($read != "") { $this->parser->append($read); } elseif (!is_resource($this->socket) || @feof($this->socket)) { $this->onError(new ConnectException("Connection went away (read)", $code = 2)); } }); $this->writeWatcher = onWritable($this->socket, $onWrite, ["enable" => !empty($this->outputBuffer)]); $connectPromisor->succeed(); }); return $this->connectPromisor->promise(); }