host = $host; $this->port = intval($port); $this->password = $password; $this->timeout = $timeout; $this->socket = null; $this->req_id = random_int(1, 0x7fffffff); } public function connect() { $this->socket = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout); if (!$this->socket) return false; stream_set_timeout($this->socket, $this->timeout); stream_set_blocking($this->socket, true); $pkt = $this->buildPacket($this->req_id, self::SERVERDATA_AUTH, $this->password); fwrite($this->socket, $pkt); $resp = $this->readPacket(); if ($resp === false) { $this->disconnect(); return false; } if (isset($resp['id']) && intval($resp['id']) == -1) { $this->disconnect(); return false; } return true; } public function disconnect() { if ($this->socket) { fclose($this->socket); $this->socket = null; } } public function sendCommand($command) { if (!$this->socket) return false; $this->req_id = ($this->req_id + 1) & 0x7fffffff; $pkt = $this->buildPacket($this->req_id, self::SERVERDATA_EXECCOMMAND, $command); fwrite($this->socket, $pkt); $out = ''; $start = time(); while (true) { $resp = $this->readPacket(); if ($resp === false) break; if (isset($resp['body'])) $out .= $resp['body']; $meta = stream_get_meta_data($this->socket); if ($meta['timed_out']) break; if (time() - $start > $this->timeout) break; if (strlen($resp['body']) < 4096) break; } return $out; } private function buildPacket($id, $type, $body) { $payload = pack('V', $id) . pack('V', $type) . $body . "\x00\x00"; $size = strlen($payload); return pack('V', $size) . $payload; } private function readPacket() { $sizeData = $this->fread_all(4); if ($sizeData === false) return false; $sizeArr = unpack('Vsize', $sizeData); $size = $sizeArr['size']; if ($size <= 0) return false; $payload = $this->fread_all($size); if ($payload === false) return false; $id = unpack('Vid', substr($payload, 0, 4))['id'] ?? 0; $type = unpack('Vtype', substr($payload, 4, 4))['type'] ?? 0; $body = substr($payload, 8, $size - 10); return ['id' => $id, 'type' => $type, 'body' => $body]; } private function fread_all($len) { $data = ''; $read = 0; $start = time(); while ($read < $len) { $chunk = @fread($this->socket, $len - $read); if ($chunk === false) return false; if ($chunk === '') { $meta = stream_get_meta_data($this->socket); if ($meta['timed_out'] || (time() - $start) > $this->timeout) return false; usleep(10000); continue; } $data .= $chunk; $read += strlen($chunk); } return $data; } }