1
0
فهرست منبع

Add socket class

Nathaniel van Diepen 7 سال پیش
والد
کامیت
2e691d287d
1فایلهای تغییر یافته به همراه160 افزوده شده و 0 حذف شده
  1. 160 0
      Net/socket.class.php

+ 160 - 0
Net/socket.class.php

@@ -0,0 +1,160 @@
+<?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){
+				$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);
+			}
+		}
+	}
+?>