Updated 'core'.
[hub.git] / application / hub / main / listener / tcp / class_TcpListener.php
1 <?php
2 /**
3  * A TCP connection listener
4  *
5  * @author              Roland Haeder <webmaster@shipsimu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Hub Developer Team
8  * @license             GNU GPL 3.0 or any newer version
9  * @link                http://www.shipsimu.org
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program. If not, see <http://www.gnu.org/licenses/>.
23  */
24 class TcpListener extends BaseListener implements Listenable {
25         /**
26          * Protected constructor
27          *
28          * @return      void
29          */
30         protected function __construct () {
31                 // Call parent constructor
32                 parent::__construct(__CLASS__);
33
34                 // Set the protocol to TCP
35                 $this->setProtocolName('tcp');
36         }
37
38         /**
39          * Creates an instance of this class
40          *
41          * @param       $nodeInstance           A NodeHelper instance
42          * @return      $listenerInstance       An instance a prepared listener class
43          */
44         public static final function createTcpListener (NodeHelper $nodeInstance) {
45                 // Get new instance
46                 $listenerInstance = new TcpListener();
47
48                 // Return the prepared instance
49                 return $listenerInstance;
50         }
51
52         /**
53          * Initializes the listener by setting up the required socket server
54          *
55          * @return      void
56          * @throws      InvalidSocketException  Thrown if the socket could not be initialized
57          */
58         public function initListener () {
59                 // Create a streaming socket, of type TCP/IP
60                 $mainSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
61
62                 // Is the socket resource valid?
63                 if (!is_resource($mainSocket)) {
64                         // Something bad happened
65                         throw new InvalidSocketException(array($this, $mainSocket), BaseListener::EXCEPTION_INVALID_SOCKET);
66                 } // END - if
67
68                 // Get socket error code for verification
69                 $socketError = socket_last_error($mainSocket);
70
71                 // Check if there was an error else
72                 if ($socketError > 0) {
73                         // Handle this socket error with a faked recipientData array
74                         $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0'));
75                         /*
76                         // Then throw again
77                         throw new InvalidSocketException(array($this, $mainSocket, $socketError, socket_strerror($socketError)), BaseListener::EXCEPTION_INVALID_SOCKET);
78                         */
79                 } // END - if
80
81                 // Set the option to reuse the port
82                 if (!socket_set_option($mainSocket, SOL_SOCKET, SO_REUSEADDR, 1)) {
83                         // Handle this socket error with a faked recipientData array
84                         $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0'));
85                         /*
86                         // Get socket error code for verification
87                         $socketError = socket_last_error($mainSocket);
88
89                         // Get error message
90                         $errorMessage = socket_strerror($socketError);
91
92                         // Shutdown this socket
93                         $this->shutdownSocket($mainSocket);
94
95                         // And throw again
96                         throw new InvalidSocketException(array($this, $mainSocket, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
97                         */
98                 } // END - if
99
100                 /*
101                  * "Bind" the socket to the given address, on given port so this means
102                  * that all connections on this port are now our resposibility to
103                  * send/recv data, disconnect, etc..
104                  */
105                 self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __METHOD__ . ':' . __LINE__ . ']: Binding to address ' . $this->getListenAddress() . ':' . $this->getListenPort());
106                 if (!socket_bind($mainSocket, $this->getListenAddress(), $this->getListenPort())) {
107                         // Handle this socket error with a faked recipientData array
108                         $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0'));
109                         /*
110                         // Get socket error code for verification
111                         $socketError = socket_last_error($mainSocket);
112
113                         // Get error message
114                         $errorMessage = socket_strerror($socketError);
115
116                         // Shutdown this socket
117                         $this->shutdownSocket($mainSocket);
118
119                         // And throw again
120                         throw new InvalidSocketException(array($this, $mainSocket, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
121                         */
122                 } // END - if
123
124                 // Start listen for connections
125                 self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __METHOD__ . ':' . __LINE__ . ']: Listening for connections.');
126                 if (!socket_listen($mainSocket)) {
127                         // Handle this socket error with a faked recipientData array
128                         $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0'));
129                         /*
130                         // Get socket error code for verification
131                         $socketError = socket_last_error($mainSocket);
132
133                         // Get error message
134                         $errorMessage = socket_strerror($socketError);
135
136                         // Shutdown this socket
137                         $this->shutdownSocket($mainSocket);
138
139                         // And throw again
140                         throw new InvalidSocketException(array($this, $mainSocket, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
141                         */
142                 } // END - if
143
144                 // Now, we want non-blocking mode
145                 self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __METHOD__ . ':' . __LINE__ . ']: Setting non-blocking mode.');
146                 if (!socket_set_nonblock($mainSocket)) {
147                         // Handle this socket error with a faked recipientData array
148                         $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0'));
149                         /*
150                         // Get socket error code for verification
151                         $socketError = socket_last_error($mainSocket);
152
153                         // Get error message
154                         $errorMessage = socket_strerror($socketError);
155
156                         // Shutdown this socket
157                         $this->shutdownSocket($mainSocket);
158
159                         // And throw again
160                         throw new InvalidSocketException(array($this, $mainSocket, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
161                         */
162                 } // END - if
163
164                 // Set the main socket
165                 $this->registerServerSocketResource($mainSocket);
166
167                 // Initialize the peer pool instance
168                 $poolInstance = ObjectFactory::createObjectByConfiguredName('node_pool_class', array($this));
169
170                 // Add main socket
171                 $poolInstance->addPeer($mainSocket, BaseConnectionHelper::CONNECTION_TYPE_SERVER);
172
173                 // And add it to this listener
174                 $this->setPoolInstance($poolInstance);
175
176                 // Initialize iterator for listening on packages
177                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('network_listen_iterator_class', array($poolInstance->getPoolEntriesInstance()));
178
179                 // Rewind it and remember it in this class
180                 $iteratorInstance->rewind();
181                 $this->setIteratorInstance($iteratorInstance);
182
183                 // Initialize the network package handler
184                 $handlerInstance = ObjectFactory::createObjectByConfiguredName('tcp_raw_data_handler_class');
185
186                 // Set it in this class
187                 $this->setHandlerInstance($handlerInstance);
188
189                 // Output message
190                 self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __METHOD__ . ':' . __LINE__ . ']: TCP listener now ready on IP ' . $this->getListenAddress() . ', port ' . $this->getListenPort() . ' for service.');
191         }
192
193         /**
194          * "Listens" for incoming network packages
195          *
196          * @return      void
197          * @throws      InvalidSocketException  If an invalid socket resource has been found
198          */
199         public function doListen () {
200                 // Get all readers
201                 $readers = $this->getPoolInstance()->getAllSingleSockets();
202                 $writers = array();
203                 $excepts = array();
204
205                 // Check if we have some peers left
206                 $left = socket_select(
207                         $readers,
208                         $writers,
209                         $excepts,
210                         0,
211                         150
212                 );
213
214                 // Some new peers found?
215                 if ($left < 1) {
216                         // Debug message
217                         //* EXTREME-NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __METHOD__ . ':' . __LINE__ . ']: left=' . $left . ',serverSocket=' . $this->getSocketResource() . ',readers=' . print_r($readers, TRUE));
218
219                         // Nothing new found
220                         return;
221                 } // END - if
222
223                 // Debug message
224                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __METHOD__ . ':' . __LINE__ . ']: serverSocket=' . $this->getSocketResource() . ',readers=' . print_r($readers, TRUE));
225
226                 // Do we have changed peers?
227                 if (in_array($this->getSocketResource(), $readers)) {
228                         /*
229                          * Then accept it, if this socket is set to non-blocking IO and the
230                          * connection is NOT sending any data, socket_read() may throw
231                          * error 11 (Resource temporary unavailable). This really nasty
232                          * because if you have blocking IO socket_read() will wait and wait
233                          * and wait ...
234                          */
235                         $newSocket = socket_accept($this->getSocketResource());
236
237                         // Debug message
238                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __METHOD__ . ':' . __LINE__ . ']: newSocket=' . $newSocket . ',serverSocket=' .$this->getSocketResource());
239
240                         // Array for timeout settings
241                         $options  = array(
242                                 // Seconds
243                                 'sec'  => $this->getConfigInstance()->getConfigEntry('tcp_socket_accept_wait_sec'),
244                                 // Milliseconds
245                                 'usec' => $this->getConfigInstance()->getConfigEntry('tcp_socket_accept_wait_usec')
246                         );
247
248                         // Set timeout to configured seconds
249                         // @TODO Does this work on Windozer boxes???
250                         if (!socket_set_option($newSocket, SOL_SOCKET, SO_RCVTIMEO, $options)) {
251                                 // Handle this socket error with a faked recipientData array
252                                 $this->handleSocketError(__METHOD__, __LINE__, $newSocket, array('0.0.0.0', '0'));
253                         } // END - if
254
255                         // Output result (only for debugging!)
256                         /*
257                         $option = socket_get_option($newSocket, SOL_SOCKET, SO_RCVTIMEO);
258                         self::createDebugInstance(__CLASS__)->debugOutput('SO_RCVTIMEO[' . gettype($option) . ']=' . print_r($option, TRUE));
259                         */
260
261                         // Enable SO_OOBINLINE
262                         if (!socket_set_option($newSocket, SOL_SOCKET, SO_OOBINLINE ,1)) {
263                                 // Handle this socket error with a faked recipientData array
264                                 $this->handleSocketError(__METHOD__, __LINE__, $newSocket, array('0.0.0.0', '0'));
265                         } // END - if
266
267                         // Set non-blocking
268                         if (!socket_set_nonblock($newSocket)) {
269                                 // Handle this socket error with a faked recipientData array
270                                 $this->handleSocketError(__METHOD__, __LINE__, $newSocket, array('0.0.0.0', '0'));
271                         } // END - if
272
273                         // Add it to the peers
274                         $this->getPoolInstance()->addPeer($newSocket, BaseConnectionHelper::CONNECTION_TYPE_INCOMING);
275
276                         // Get peer name
277                         if (!socket_getpeername($newSocket, $peerName)) {
278                                 // Handle this socket error with a faked recipientData array
279                                 $this->handleSocketError(__METHOD__, __LINE__, $newSocket, array('0.0.0.0', '0'));
280                         } // END - if
281
282                         // Get node instance
283                         $nodeInstance = Registry::getRegistry()->getInstance('node');
284
285                         // Create a faked package data array
286                         $packageData = array(
287                                 NetworkPackage::PACKAGE_DATA_SENDER    => $peerName . ':0',
288                                 NetworkPackage::PACKAGE_DATA_RECIPIENT => $nodeInstance->getSessionId(),
289                                 NetworkPackage::PACKAGE_DATA_STATUS    => NetworkPackage::PACKAGE_STATUS_FAKED
290                         );
291
292                         // Get a connection info instance
293                         $infoInstance = ConnectionInfoFactory::createConnectionInfoInstance($this->getProtocolName(), 'listener');
294
295                         // Will the info instance with listener data
296                         $infoInstance->fillWithListenerInformation($this);
297
298                         // Get a socket registry
299                         $registryInstance = SocketRegistryFactory::createSocketRegistryInstance();
300
301                         // Register the socket with the registry and with the faked array
302                         $registryInstance->registerSocket($infoInstance, $newSocket, $packageData);
303                 } // END - if
304
305                 // Do we have to rewind?
306                 if (!$this->getIteratorInstance()->valid()) {
307                         // Rewind the list
308                         $this->getIteratorInstance()->rewind();
309                 } // END - if
310
311                 // Get the current value
312                 $currentSocket = $this->getIteratorInstance()->current();
313
314                 // Handle it here, if not main server socket
315                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __METHOD__ . ':' . __LINE__ . ']: currentSocket=' . $currentSocket[BasePool::SOCKET_ARRAY_RESOURCE] . ',type=' . $currentSocket[BasePool::SOCKET_ARRAY_CONN_TYPE] . ',serverSocket=' . $this->getSocketResource());
316                 if (($currentSocket[BasePool::SOCKET_ARRAY_CONN_TYPE] != BaseConnectionHelper::CONNECTION_TYPE_SERVER) && ($currentSocket[BasePool::SOCKET_ARRAY_RESOURCE] != $this->getSocketResource())) {
317                         // ... or else it will raise warnings like 'Transport endpoint is not connected'
318                         $this->getHandlerInstance()->processRawDataFromResource($currentSocket);
319                 } // END - if
320
321                 // Advance to next entry. This should be the last line.
322                 $this->getIteratorInstance()->next();
323         }
324
325         /**
326          * Checks whether the listener would accept the given package data array
327          *
328          * @param       $packageData    Raw package data
329          * @return      $accepts                Whether this listener does accept
330          * @throws      UnsupportedOperationException   If this method is called
331          */
332         public function ifListenerAcceptsPackageData (array $packageData) {
333                 // Please don't call this
334                 throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION);
335         }
336 }
337
338 // [EOF]
339 ?>