123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- <?php
- namespace Juju\Net;
- use Juju\Events;
- require_once(dirname(__DIR__).'/events.trait.php');
- class Socket {
- use Events;
- private $socket;
- private $clients;
- private $dsn;
- private $socket_type;
- private $open;
- const SERVER = 0;
- const CLIENT = 1;
- public static function from(string $dsnstring, bool $listen = false){
- return new Socket($dsnstring, $listen);
- }
- private function __construct(string $dsnstring, bool $listen = false){
- $parts = explode('://', $dsnstring);
- $dsnstring = $parts[1];
- $dsn = explode(';', $dsnstring);
- $dsn = array_reduce($dsn, function($dsn, $item){
- $item = explode('=', $item);
- $dsn[$item[0]] = $item[1];
- return $dsn;
- });
- $this->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);
- }
- }
- }
- ?>
|