maxChild; $this->maxChild = intval ($val); return $this; } /** Set/get the read mode : text or binary * @param string|null $readMode The mode to set (or get if null) */ public function readMode ($readMode = null) { if ($readMode === null) return $this->readMode; if ($readMode !== "text" && $readMode !== "binary") throw new \Exception ("Invalid readMode provided (nor text nor binary)", 500); $this->readMode = $readMode; return $this; } /** Set the address, port and handler that will be enabled by loop * @param string $address The server address (can be 0.0.0.0 for all IPv4 * interfaces or :: for all the IPv4 and IPv6 interfaces) * @param integer $port The port to listen * @param callable $handler The handler that will be called when a client is * connected to the address:port */ public function init ($address, $port, $handler) { $this->handlers["$address:$port"] = $handler; $this->addresses[] = $address; $this->ports[] = $port; return $this; } /** Start the main loop after the init */ public function loop () { foreach ($this->addresses as $key => $address) { $port = $this->ports[$key]; if (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) $sockServer[$key] = stream_socket_server ("tcp://$address:$port", $errno, $errstr); elseif (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) $sockServer[$key] = stream_socket_server ("tcp://[$address]:$port", $errno, $errstr); else throw new \Exception ("Can't create socket : invalid address provided"); if ($sockServer[$key] === false) { throw new \Exception ("Can't create socket : $errstr"); } //printf ("Listening on %s:%d...\n", $address, $port); } $nbChild = 1; while (1) { // Watch if a child process is zombie, then destroy it if (pcntl_wait ($status, WNOHANG) > 0) $nbChild--; $read = $sockServer; $write = null; $except = null; if (stream_select ($read, $write, $except, 0, 200000) < 1) continue; foreach ($read as $sock) { $client = stream_socket_accept ($sock); $name = stream_socket_get_name ($client, false); $pos = strrpos ($name, ":"); $localPort = substr ($name, $pos+1); $localAddress = substr ($name, 0, $pos); $name = stream_socket_get_name ($client, true); $pos = strrpos ($name, ":"); $port = substr ($name, $pos+1); $address = substr ($name, 0, $pos); $handlerAddress = $localAddress; if (! key_exists ("$handlerAddress:$localPort", $this->handlers)) { // If the address of the handler doesn't exists, the socket is maybe // waiting on all the interfaces addresses if (filter_var ($localAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) $handlerAddress = "0.0.0.0"; elseif (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) $handlerAddress = "::"; if (! key_exists ("$handlerAddress:$localPort", $this->handlers)) { printf ("Can't find the handler for %s:%d\n", $localAddress, $localPort); continue; } } //echo "Enter $address:$port > $localAddress:$localPort\n"; $handler = $this->handlers["$handlerAddress:$localPort"]; // We got a connection fork to manage it if ($nbChild > $this->maxChild) { printf ("Too much child process %d : Abort the connection !!\n", $nbChild); @stream_socket_shutdown ($client, STREAM_SHUT_RDWR); continue; } $pid = pcntl_fork(); if ($pid == -1) { throw new \Exception ("TCPServer can not fork", 500); } else if ($pid) { // parent process: return to the main loop $nbChild++; continue; } // In the child. Will call the handler with the actual tcpserver object // as parameter $this->parent = false; $this->socket = $client; if (is_array ($handler)) { $object = $handler[0]; $method = $handler[1]; $object->$method ($this); } else { $function = $handler; $function ($this); } exit; } } } /** In child, get the socket to direct access * @return resource The socket with the client */ public function getSock () { if ($this->parent === true) throw new \Exception ("Can not return the socket in parent mode", 500); if ($this->socket === null) throw new \Exception ("Can not send to client : not connected", 500); return $this->socket; } /** Get an array with the peer address, peer port, local address and local * port * @return array array ("peer address", peer port, "local address", local * port) */ public function getInfo () { if ($this->parent === true) throw new \Exception ("Can not get info in parent mode", 500); if ($this->socket === null) throw new \Exception ("Can not send to client : not connected", 500); $name = stream_socket_get_name ($this->socket, false); $pos = strrpos ($name, ":"); $localPort = substr ($name, $pos+1); $localAddress = substr ($name, 0, $pos); $name = stream_socket_get_name ($this->socket, true); $pos = strrpos ($name, ":"); $port = substr ($name, $pos+1); $address = substr ($name, 0, $pos); return array ($address, $port, $localAddress, $localPort); } /** Activate the SSL connection * Put the socket in blocking mode, as it is mandatory to have SSL connection * @param boolean $val True to activate, false to disable SSL * @param integer $cryptoMethod The cryptoMethod allowed */ public function cryptoEnable ($val, $cryptoMethod = STREAM_CRYPTO_METHOD_TLS_SERVER) { if ($this->socket === null) throw new \Exception ("Can not send to server $this->ipOrName : ". "The server is not connected", 500); // Setting the options allow the IP to be decided by the connect and valid // the certificate of the server by the name $options = array ("ssl" => array ( "verify_peer_name" => false, )); stream_set_blocking ($this->socket, true); stream_context_set_option ($this->socket, $options); return stream_socket_enable_crypto ($this->socket, !!$val, $cryptoMethod); } /** Set context SSL option. * @param array $options The ssl array to set */ public function setSSLOptions ($options) { if ($this->socket === null) throw new \Exception ("Can not send to server $this->ipOrName : ". "The server is not connected", 500); return stream_context_set_option ($this->socket, array ("ssl" => $options)); } /** Send data to the client * @param mixed $data The data to send * @return the length of data sent */ public function send ($data) { if ($this->parent === true) throw new \Exception ("Can not send data in parent mode", 500); if ($this->socket === null) throw new \Exception ("Can not send to client : not connected", 500); $length = strlen ($data); $sentLen = @fwrite ($this->socket, $data); if ($sentLen < $length) throw new \Exception ("Can not send data to client", 500); return $sentLen; } /** Read the data from the client. * The connection must be established * Use the readMode in text or binary (text by default) * In text mode, the read return when found the first \r or the first \n. * @param integer $maxLength Limit the length of the data from the server * @return The content */ public function read ($maxLength = 1024) { if ($this->parent === true) throw new \Exception ("Can not read data in parent mode", 500); if ($this->socket === null) throw new \Exception ("Can not read from client : not connected", 500); if ($this->readMode === "text") { $read = stream_get_line ($this->socket, $maxLength, "\r\n"); if ($read === false) throw new \Exception ("Can not read from client : ". error_get_last ()["message"], 500); } else { $read = @fread ($this->socket, $maxLength); if ($read === false) throw new \Exception ("Can not read from client : ". error_get_last ()["message"], 500); } return $read; } /** Disconnect the socket */ public function disconnect () { if ($this->parent === true) throw new \Exception ("Can not send data in parent mode", 500); if ($this->socket === null) throw new \Exception ("Can not disconnect client : not connected", 500); @stream_socket_shutdown ($this->socket, STREAM_SHUT_RDWR); $this->socket = null; } }