/** * @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 static function onEmptyRead($state) { if (!\is_resource($state->socket) || @\feof($state->socket)) { $state->isDead |= STREAM_SHUT_RD; amp\cancel($state->readWatcherId); // sink the buffer Client::onRead($state); // discard all readOperations that were left after buffer is empty $ops = $state->readOperations; $state->readOperations = []; foreach ($ops as $op) { $op->promisor->succeed(); } } }