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){ if(is_null($this->port)){ $res = socket_bind($this->socket, $this->address); $this->throw_if($res); if(!file_exists($this->address)){ throw new \Exception('Unable to create socket file'); } chmod($this->address, 0702); unlink($this->address); // Delete when we are done }else{ $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){ if(is_null($this->port)){ $res = socket_connect($this->socket, $this->address); }else{ $res = socket_connect($this->socket, $this->address, $this->port); } $this->throw_if($res); }else{ throw new \Exception("Invalid socket type {$this->socket_type}"); } $this->open = true; $this->fire('open', $this); return $this; } public function handle(int $length = 2048, bool $binary = false){ if(!$this->open){ $this->open(); } socket_set_nonblock($this->socket); if($this->socket_type == static::SERVER){ $regex = $binary ? '/\0/' : '/[\n\r]/'; $type = $binary ? PHP_BINARY_READ : PHP_NORMAL_READ; do{ $socket = socket_accept($this->socket); if($socket === false){ usleep(100); }elseif($socket > 0){ socket_set_nonblock($socket); $client = [$socket, '']; $this->clients[] = $client; $this->fire('connect', $this, $client); }else{ $error = trim(socket_strerror($socket)); if($error !== 'A non-blocking socket operation could not be completed immediately.'){ $this->fire('error', $this, new \Exception($error)); $this->open = false; } } $this->fire('tick', $this); if($this->open){ foreach($this->clients as $client){ $data = socket_read($client[0], $length, $type); if($data === false){ $this->drop($client); }else{ if(strlen($data) > 0 && $this->fire('data', $this, $data, $client) !== false){ $client[1] .= $data; } $pos = preg_match($regex, $data, $matches, PREG_OFFSET_CAPTURE) == 1 ? $matches[0][1] : false; if($pos === strlen($data) - 1){ $this->fire('read', $this, substr($client[1], 0, -1), $client); $client[1] = ''; } } $this->fire('tick', $this); if($this->open === false){ break; } } } }while($this->open); }elseif($this->socket_type == static::CLIENT){ while($this->open && $this->read($length, $binary) !== false){}; } socket_set_block($this->socket); $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]/'; $type = $binary ? PHP_BINARY_READ : PHP_NORMAL_READ; $line = ''; do{ $data = socket_read($this->socket, $length, $type); 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; }while($pos !== strlen($data) - 1 && $this->open !== false); $line = substr($line, 0, -1); $this->fire('read', $this, $line); return $line; } public function client_write(array $client, $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($client[0], $data.$stop)); } return $this; } public function close(bool $event = false){ foreach($this->clients as $client){ $this->drop($client); } socket_close($this->socket); $this->open = false; $this->fire('close', $this); return $this; } public function drop($client){ socket_close($client[0]); if(in_array($client, $this->clients)){ array_splice($this->clients, array_search($client, $this->clients), 1); $this->fire('disconnect', $this, $client); } } } ?>