dsn = [ 'protocol'=> $parts[0] ?? null, 'domain'=> strtoupper($dsn['domain']) ?? 'UNIX', 'type'=> strtoupper($dsn['type']) ?? 'STREAM', 'address'=> $dsn['address'] ?? '127.0.0.1', 'port'=> $dsn['port'] ?? null, 'max_connections'=> $dsn['max_connections'] ?? 5 ]; $this->socket_type = $listen ? static::SERVER : static::CLIENT; $this->open = false; if($listen){ $this->clients = []; } } public function __get($name){ switch($name){ case 'socket':case 'dsn':case 'socket_type':case 'open': return $this->$name; break; case 'protocol':case 'domain':case 'type':case 'address':case 'port':case 'max_connections': return $this->dsn[$name]; break; case 'error': return socket_strerror(socket_last_error()); break; default: throw new \Exception("Property {$name} does not exist"); } } public function __set($name, $value){ throw new \Exception("Property {$name} does not exist or is read only"); } private function throw_if($bool){ if($bool === false){ $this->throw(); } } private function throw(){ $error = new \Exception($this->error); if($this->fire('error', $this, $error) !== false){ throw $error; } } public function open(){ $this->socket = socket_create(constant("AF_{$this->dsn['protocol']}"), constant("SOCK_{$this->dsn['type']}"), is_null($this->dsn['protocol']) ? 0 : getprotobyname($this->dsn['protocol'])); $this->throw_if($this->socket); if($this->socket_type == static::SERVER){ $this->throw_if(socket_bind($this->socket, $this->address, $this->port)); $this->fire('bind', $this); $this->throw_if(socket_listen($this->socket, $this->max_connections)); $this->fire('listen', $this); }elseif($this->socket_type == static::CLIENT){ $this->throw_if(socket_connect($this->socket, $this->address, $this->port)); }else{ throw new \Exception("Invalid socket type {$this->socket_type}"); } $this->open = true; $this->fire('open', $this); return $this; } public function handle(){ if(!$this->open){ $this->open(); } if($this->socket_type == static::SERVER){ socket_set_nonblock($this->socket); do{ $client = socket_accept($this->socket); if($client === false){ $this->open = $this->error !== 'A non-blocking socket operation could not be completed immediately.'; }elseif(!is_null($client)){ socket_set_nonblock($client); $this->client = $client; $this->fire('connect', $this); while($this->open && $this->read() !== false){}; socket_set_block($client); socket_close($client); $this->client = null; $this->fire('disconnect', $this); } }while($this->open); socket_set_block($this->socket); }elseif($this->socket_type == static::CLIENT){ socket_set_nonblock($this->client); while($this->open && $this->read() !== false){}; socket_set_block($this->client); } $this->close(true); return $this; } public function write(string $data, bool $binary = false){ if(!$this->open){ throw new \Exception("You can't write to a socket that isn't open"); } $stop = $binary ? "\0" : "\n"; if($this->fire('write', $this, $data) !== false){ $this->throw_if(socket_write($this->socket, $data.$stop)); } return $this; } public function read(int $length = 2048, bool $binary = false){ if(!$this->open){ return false; } $regex = $binary ? '/\0/' : '/[\n\r]/'; $line = ''; do{ $data = socket_read($this->socket, $length, $binary ? PHP_BINARY_READ : PHP_NORMAL_READ); if($data === false){ $this->open = false; }elseif(strlen($data) > 0 && $this->fire('data', $this, $data) !== false){ $line .= $data; } $pos = preg_match($regex, $data, $matches, PREG_OFFSET_CAPTURE) == 1 ? $matches[0][1] : false; $this->fire('tick', $this); }while($pos !== strlen($data) - 1 && $this->open !== false); $line = substr($line, 0, -1); $this->fire('read', $this, $line); return $line; } public function close(bool $event = false){ $this->drop(); socket_close($this->socket); $this->open = false; $this->fire('close', $this); return $this; } public function drop($client){ if(!is_null($client)){ socket_close($client); } } } ?>