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