1<?php 2/** 3 * This file is part of the FreeDSx Socket package. 4 * 5 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace FreeDSx\Socket; 12 13use FreeDSx\Socket\Exception\ConnectionException; 14 15/** 16 * TCP socket server to accept client connections. 17 * 18 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 19 */ 20class SocketServer extends Socket 21{ 22 /** 23 * @var array 24 */ 25 protected $serverOpts = [ 26 'ssl_cert' => null, 27 'ssl_cert_key' => null, 28 'ssl_cert_passphrase' => null, 29 'ssl_crypto_type' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER | STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | STREAM_CRYPTO_METHOD_TLS_SERVER, 30 'ssl_validate_cert' => false, 31 'idle_timeout' => 600, 32 ]; 33 34 /** 35 * @var Socket[] 36 */ 37 protected $clients = []; 38 39 /** 40 * @param array $options 41 */ 42 public function __construct(array $options = []) 43 { 44 parent::__construct(null, \array_merge($this->serverOpts, $options)); 45 } 46 47 /** 48 * Create the socket server and bind to a specific port to listen for clients. 49 * 50 * @param string $ip 51 * @param int $port 52 * @return $this 53 * @throws ConnectionException 54 * @internal param string $ip 55 */ 56 public function listen(string $ip, int $port) 57 { 58 $flags = STREAM_SERVER_BIND; 59 if ($this->options['transport'] !== 'udp') { 60 $flags |= STREAM_SERVER_LISTEN; 61 } 62 $socket = @\stream_socket_server( 63 $this->options['transport'].'://'.$ip.':'.$port, 64 $this->errorNumber, 65 $this->errorMessage, 66 $flags, 67 $this->createSocketContext() 68 ); 69 if ($socket === false) { 70 throw new ConnectionException(sprintf( 71 'Unable to open %s socket (%s): %s', 72 \strtoupper($this->options['transport']), 73 $this->errorNumber, 74 $this->errorMessage 75 )); 76 } 77 $this->socket = $socket; 78 79 return $this; 80 } 81 82 /** 83 * @param int $timeout 84 * @return null|Socket 85 */ 86 public function accept($timeout = -1) : ?Socket 87 { 88 $socket = @\stream_socket_accept($this->socket, $timeout); 89 if (\is_resource($socket)) { 90 $socket = new Socket($socket, \array_merge($this->options, [ 91 'timeout_read' => $this->options['idle_timeout'] 92 ])); 93 $this->clients[] = $socket; 94 } 95 96 return $socket instanceof Socket ? $socket : null; 97 } 98 99 /** 100 * Receive data from a UDP based socket. Optionally get the IP address the data was received from. 101 * 102 * @todo Buffer size should be adjustable. Max UDP packet size is 65507. Currently this avoids possible truncation. 103 * @param null $ipAddress 104 * @return null|string 105 */ 106 public function receive(&$ipAddress = null) 107 { 108 $this->block(true); 109 110 return \stream_socket_recvfrom($this->socket, 65507, 0, $ipAddress); 111 } 112 113 /** 114 * @return Socket[] 115 */ 116 public function getClients() 117 { 118 return $this->clients; 119 } 120 121 /** 122 * @param Socket $socket 123 */ 124 public function removeClient(Socket $socket) : void 125 { 126 if (($index = \array_search($socket, $this->clients, true)) !== false) { 127 unset($this->clients[$index]); 128 } 129 } 130 131 /** 132 * Create the socket server. Binds and listens on a specific port 133 * 134 * @param string $ip 135 * @param int $port 136 * @param array $options 137 * @return SocketServer 138 * @throws ConnectionException 139 */ 140 public static function bind(string $ip, int $port, array $options = []) : SocketServer 141 { 142 return (new self($options))->listen($ip, $port); 143 } 144 145 /** 146 * Create a TCP based socket server. 147 * 148 * @param string $ip 149 * @param int $port 150 * @param array $options 151 * @return SocketServer 152 * @throws ConnectionException 153 */ 154 public static function bindTcp(string $ip, int $port, array $options = []) : SocketServer 155 { 156 return static::bind($ip, $port, \array_merge($options, ['transport' => 'tcp'])); 157 } 158 159 /** 160 * Created a UDP based socket server. 161 * 162 * @param string $ip 163 * @param int $port 164 * @param array $options 165 * @return SocketServer 166 * @throws ConnectionException 167 */ 168 public static function bindUdp(string $ip, int $port, array $options = []) : SocketServer 169 { 170 return static::bind($ip, $port, \array_merge($options, ['transport' => 'udp'])); 171 } 172} 173