f1dfa8305cac5a90949a5e56e31f7ab40afe46aa
[hub.git] / application / hub / classes / helper / connection / ipv4 / class_BaseIpV4ConnectionHelper.php
1 <?php
2 /**
3  * A ??? connection helper class
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.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.ship-simu.org
10  * @todo                Find an interface for hub helper
11  *
12  * This program is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation, either version 3 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program. If not, see <http://www.gnu.org/licenses/>.
24  */
25 class BaseIpV4ConnectionHelper extends BaseConnectionHelper {
26         /**
27          * Port number used
28          */
29         private $connectionPort = 0;
30
31         /**
32          * Protected constructor
33          *
34          * @param       $className      Name of implementing class
35          * @return      void
36          */
37         protected function __construct ($className) {
38                 // Call parent constructor
39                 parent::__construct($className);
40         }
41
42         /**
43          * Getter for port number to satify HandleableProtocol
44          *
45          * @return      $connectionPort The port number
46          */
47         public final function getConnectionPort () {
48                 return $this->connectionPort;
49         }
50
51         /**
52          * Setter for port number to satify HandleableProtocol
53          *
54          * @param       $connectionPort The port number
55          * @return      void
56          */
57         protected final function setConnectionPort ($connectionPort) {
58                 $this->connectionPort = $connectionPort;
59         }
60
61         /**
62          * Initializes the current connection
63          *
64          * @return      void
65          * @throws      SocketOptionException   If setting any socket option fails
66          */
67         protected function initConnection () {
68                 // Get socket resource
69                 $socketResource = $this->getSocketResource();
70
71                 // Set the option to reuse the port
72                 if (!socket_set_option($socketResource, SOL_SOCKET, SO_REUSEADDR, 1)) {
73                         // Handle this socket error with a faked recipientData array
74                         $this->handleSocketError(__METHOD__, __LINE__, $socketResource, array('0.0.0.0', '0'));
75
76                         // And throw again
77                         // @TODO Move this to the socket error handler
78                         throw new SocketOptionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
79                 } // END - if
80
81                 /*
82                  * Set socket to non-blocking mode before trying to establish a link to
83                  * it. This is now the default behaviour for all connection helpers who
84                  * call initConnection(); .
85                  */
86                 if (!socket_set_nonblock($socketResource)) {
87                         // Handle this socket error with a faked recipientData array
88                         $helperInstance->handleSocketError(__METHOD__, __LINE__, $socketResource, array('0.0.0.0', '0'));
89
90                         // And throw again
91                         throw new SocketOptionException(array($helperInstance, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
92                 } // END - if
93
94                 // Last step: mark connection as initialized
95                 $this->setIsInitialized(TRUE);
96         }
97
98         /**
99          * Attempts to connect to a peer by given IP number and port from a valid
100          * unlData array with currently configured timeout.
101          *
102          * @param       $unlData                Valid UNL data array
103          * @return      $isConnected    Whether the connection went fine
104          * @see         Please see http://de.php.net/manual/en/function.socket-connect.php#84465 for original code
105          * @todo        Rewrite the while() loop to a iterator to not let the software stay very long here
106          */
107         protected function connectToPeerByUnlData (array $unlData) {
108                 // Only call this if the connection is initialized by initConnection()
109                 assert($this->isInitialized());
110
111                 // Is unlData complete?
112                 assert(isset($unlData[UniversalNodeLocator::UNL_PART_PROTOCOL]));
113                 assert(isset($unlData[UniversalNodeLocator::UNL_PART_ADDRESS]));
114                 assert(isset($unlData[UniversalNodeLocator::UNL_PART_PORT]));
115
116                 // "Cache" socket resource and timeout config
117                 $socketResource = $this->getSocketResource();
118                 $timeout = $this->getConfigInstance()->getConfigEntry('socket_timeout_seconds');
119
120                 // Debug output
121                 self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: Trying to connect to ' . $unlData[UniversalNodeLocator::UNL_PART_ADDRESS] . ':' . $unlData[UniversalNodeLocator::UNL_PART_PORT] . ' with socketResource[' . gettype($socketResource) . ']=' . $socketResource . ' ...');
122
123                 // Get current time
124                 $hasTimedOut = FALSE;
125                 $time = time();
126
127                 // Try to connect until it is connected
128                 while ($isConnected = !@socket_connect($socketResource, $unlData[UniversalNodeLocator::UNL_PART_ADDRESS], $unlData[UniversalNodeLocator::UNL_PART_PORT])) {
129                         // Get last socket error
130                         $socketError = socket_last_error($socketResource);
131
132                         // Log error code and status
133                         /* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: socketError=' . $socketError . ',isConnected=' . intval($isConnected));
134
135                         // Skip any errors which may happen on non-blocking connections
136                         if (($socketError == SOCKET_EINPROGRESS) || ($socketError == SOCKET_EALREADY)) {
137                                 // Now, is that attempt within parameters?
138                                 if ((time() - $time) >= $timeout) {
139                                         // Debug message
140                                         /* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: timeout=' . $timeout .' reached, connection attempt failed.');
141
142                                         // Didn't work within timeout
143                                         $isConnected = FALSE;
144                                         $hasTimedOut = TRUE;
145                                         break;
146                                 } // END - if
147
148                                 // Sleep about one second
149                                 $this->idle(1000);
150                         } elseif ($socketError != 0) {
151                                 // Debug message
152                                 /* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: socketError=' . $socketError . ' detected.');
153
154                                 // Stop on everything else pronto
155                                 $isConnected = FALSE;
156                                 break;
157                         }
158                 } // END - while
159
160                 /*
161                  * All IPv4-based connections are non-blocking used by this program or
162                  * else the PHP process will "hang" until a peer connects which is not
163                  * what is wanted here. This means, that all connections will end with
164                  * isConnected=FALSE here.
165                  */
166                 if (($hasTimedOut === FALSE) && ($socketError == SOCKET_EINPROGRESS)) {
167                         // A "connection in progress" has not timed out. All fine.
168                         $isConnected = TRUE;
169
170                         // Clear error
171                         socket_clear_error($socketResource);
172                 } // END - if
173
174                 // Log error code
175                 /* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: socketError=' . $socketError . ',isConnected=' . intval($isConnected) . ',hasTimedOut=' . intval($hasTimedOut) . ' after while() loop.');
176
177                 // Is the peer connected?
178                 if (($isConnected === TRUE) || (($socketError == SOCKET_EINPROGRESS) && ($hasTimedOut === FALSE))) {
179                         // Connection is fully established here, so change the state.
180                         PeerStateFactory::createPeerStateInstanceByName('connected', $this);
181                 } else {
182                         /*
183                          * There was a problem connecting to the peer (this state is a meta
184                          * state until the error handler has found the real cause).
185                          */
186                         PeerStateFactory::createPeerStateInstanceByName('problem', $this);
187                 }
188
189                 // Return status
190                 return $isConnected;
191         }
192
193         /**
194          * Marks this connection as shutted down
195          *
196          * @return      void
197          */
198         protected final function markConnectionShuttedDown () {
199                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: ' . $this->__toString() . ' has been marked as shutted down');
200                 $this->shuttedDown = TRUE;
201
202                 // And remove the (now invalid) socket
203                 $this->setSocketResource(FALSE);
204         }
205
206         // ************************************************************************
207         //                 Socket error handler call-back methods
208         // ************************************************************************
209
210         /**
211          * Handles socket error 'connection timed out', but does not clear it for
212          * later debugging purposes.
213          *
214          * @param       $socketResource         A valid socket resource
215          * @param       $unlData                        A valid UNL data array
216          * @return      void
217          * @throws      SocketConnectionException       The connection attempts fails with a time-out
218          */
219         protected function socketErrorConnectionTimedOutHandler ($socketResource, array $unlData) {
220                 // Get socket error code for verification
221                 $socketError = socket_last_error($socketResource);
222
223                 // Get error message
224                 $errorMessage = socket_strerror($socketError);
225
226                 // Shutdown this socket
227                 $this->shutdownSocket($socketResource);
228
229                 // Throw it again
230                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
231         }
232
233         /**
234          * Handles socket error 'resource temporary unavailable', but does not
235          * clear it for later debugging purposes.
236          *
237          * @param       $socketResource         A valid socket resource
238          * @param       $unlData                        A valid UNL data array
239          * @return      void
240          * @throws      SocketConnectionException       The connection attempts fails with a time-out
241          */
242         protected function socketErrorResourceUnavailableHandler ($socketResource, array $unlData) {
243                 // Get socket error code for verification
244                 $socketError = socket_last_error($socketResource);
245
246                 // Get error message
247                 $errorMessage = socket_strerror($socketError);
248
249                 // Shutdown this socket
250                 $this->shutdownSocket($socketResource);
251
252                 // Throw it again
253                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
254         }
255
256         /**
257          * Handles socket error 'connection refused', but does not clear it for
258          * later debugging purposes.
259          *
260          * @param       $socketResource         A valid socket resource
261          * @param       $unlData                        A valid UNL data array
262          * @return      void
263          * @throws      SocketConnectionException       The connection attempts fails with a time-out
264          */
265         protected function socketErrorConnectionRefusedHandler ($socketResource, array $unlData) {
266                 // Get socket error code for verification
267                 $socketError = socket_last_error($socketResource);
268
269                 // Get error message
270                 $errorMessage = socket_strerror($socketError);
271
272                 // Shutdown this socket
273                 $this->shutdownSocket($socketResource);
274
275                 // Throw it again
276                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
277         }
278
279         /**
280          * Handles socket error 'no route to host', but does not clear it for later
281          * debugging purposes.
282          *
283          * @param       $socketResource         A valid socket resource
284          * @param       $unlData                        A valid UNL data array
285          * @return      void
286          * @throws      SocketConnectionException       The connection attempts fails with a time-out
287          */
288         protected function socketErrorNoRouteToHostHandler ($socketResource, array $unlData) {
289                 // Get socket error code for verification
290                 $socketError = socket_last_error($socketResource);
291
292                 // Get error message
293                 $errorMessage = socket_strerror($socketError);
294
295                 // Shutdown this socket
296                 $this->shutdownSocket($socketResource);
297
298                 // Throw it again
299                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
300         }
301
302         /**
303          * Handles socket error 'operation already in progress' which happens in
304          * method connectToPeerByUnlData() on timed out connection
305          * attempts.
306          *
307          * @param       $socketResource         A valid socket resource
308          * @param       $unlData                        A valid UNL data array
309          * @return      void
310          * @throws      SocketConnectionException       The connection attempts fails with a time-out
311          */
312         protected function socketErrorOperationAlreadyProgressHandler ($socketResource, array $unlData) {
313                 // Get socket error code for verification
314                 $socketError = socket_last_error($socketResource);
315
316                 // Get error message
317                 $errorMessage = socket_strerror($socketError);
318
319                 // Half-shutdown this socket (see there for difference to shutdownSocket())
320                 $this->halfShutdownSocket($socketResource);
321
322                 // Throw it again
323                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
324         }
325
326         /**
327          * Handles socket error 'connection reset by peer', but does not clear it for
328          * later debugging purposes.
329          *
330          * @param       $socketResource         A valid socket resource
331          * @param       $unlData                        A valid UNL data array
332          * @return      void
333          * @throws      SocketConnectionException       The connection attempts fails with a time-out
334          */
335         protected function socketErrorConnectionResetByPeerHandler ($socketResource, array $unlData) {
336                 // Get socket error code for verification
337                 $socketError = socket_last_error($socketResource);
338
339                 // Get error message
340                 $errorMessage = socket_strerror($socketError);
341
342                 // Shutdown this socket
343                 $this->shutdownSocket($socketResource);
344
345                 // Throw it again
346                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
347         }
348
349         /**
350          * Handles socket "error" 'operation now in progress' which can be safely
351          * passed on with non-blocking connections.
352          *
353          * @param       $socketResource         A valid socket resource
354          * @param       $unlData                        A valid UNL data array
355          * @return      void
356          */
357         protected function socketErrorOperationInProgressHandler ($socketResource, array $unlData) {
358                 self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: Operation is now in progress, this is usual for non-blocking connections and is no bug.');
359         }
360 }
361
362 // [EOF]
363 ?>