]> git.mxchange.org Git - hub.git/blob - application/hub/main/helper/connection/class_BaseConnectionHelper.php
d7ad4ee35274342ec066fbfb72de7175a8d04b5a
[hub.git] / application / hub / main / helper / connection / class_BaseConnectionHelper.php
1 <?php
2 /**
3  * A general ConnectionHelper class
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2011 Hub Developer Team
8  * @license             GNU GPL 3.0 or any newer version
9  * @link                http://www.ship-simu.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 BaseConnectionHelper extends BaseHubHelper implements Registerable, ProtocolHandler {
25         // Exception codes
26         const EXCEPTION_UNSUPPORTED_ERROR_HANDLER = 0x900;
27
28         /**
29          * Protocol used
30          */
31         private $protocol = 'invalid';
32
33         /**
34          * Port number used
35          */
36         private $port = 0;
37
38         /**
39          * (IP) Adress used
40          */
41         private $address = 0;
42
43         /**
44          * Sent data in bytes
45          */
46         private $sentData = 0;
47
48         /**
49          * Difference
50          */
51         private $diff = 0;
52
53         /**
54          * Wether this connection is shutted down
55          */
56         private $shuttedDown = false;
57
58         /**
59          * Currently queued chunks
60          */
61         private $queuedChunks = array();
62
63         /**
64          * Current final hash
65          */
66         private $currentFinalHash = '';
67
68         /**
69          * Protected constructor
70          *
71          * @param       $className      Name of the class
72          * @return      void
73          */
74         protected function __construct ($className) {
75                 // Call parent constructor
76                 parent::__construct($className);
77
78                 // Initialize output stream
79                 $streamInstance = ObjectFactory::createObjectByConfiguredName('node_raw_data_output_stream_class');
80
81                 // And add it to this connection helper
82                 $this->setOutputStreamInstance($streamInstance);
83
84                 // Init state which sets the state to 'init'
85                 $this->initState();
86
87                 // Register this connection helper
88                 Registry::getRegistry()->addInstance('connection', $this);
89         }
90
91         /**
92          * Getter for real class name, overwrites generic method and is final
93          *
94          * @return      $class  Name of this class
95          */
96         public final function __toString () {
97                 // Class name representation
98                 $class = self::getConnectionClassName($this->getAddress(), $this->getPort(), parent::__toString());
99
100                 // Return it
101                 return $class;
102         }
103
104         /**
105          * Static "getter" for this connection class' name
106          *
107          * @param       $address        IP address
108          * @param       $port           Port number
109          * @param       $className      Original class name
110          * @return      $class          Expanded class name
111          */
112         public static function getConnectionClassName ($address, $port, $className) {
113                 // Construct it
114                 $class = $address . ':' . $port . ':' . $className;
115
116                 // ... and return it
117                 return $class;
118         }
119
120         /**
121          * Initializes the peer's state which sets it to 'init'
122          *
123          * @return      void
124          */
125         private function initState() {
126                 /*
127                  * Get the state factory and create the initial state, we don't need
128                  * the state instance here
129                  */
130                 PeerStateFactory::createPeerStateInstanceByName('init', $this);
131         }
132
133         /**
134          * "Getter" for raw data from a package array. A fragmenter is used which
135          * will returns us only so many raw data which fits into the back buffer.
136          * The rest is being held in a back-buffer and waits there for the next
137          * cycle and while be then sent.
138          *
139          * This method does 4 simple steps:
140          * 1) Aquire fragmenter object instance from the factory
141          * 2) Handle over the package data array to the fragmenter
142          * 3) Request a chunk
143          * 4) Finally return the chunk (array) to the caller
144          *
145          * @param       $packageData    Raw package data array
146          * @return      $chunkData              Raw data chunk
147          */
148         private function getRawDataFromPackageArray (array $packageData) {
149                 // Get the fragmenter instance
150                 $fragmenterInstance = FragmenterFactory::createFragmenterInstance('package');
151
152                 // Implode the package data array and fragement the resulting string, returns the final hash
153                 $finalHash = $fragmenterInstance->fragmentPackageArray($packageData, $this);
154                 if ($finalHash !== true) {
155                         $this->currentFinalHash = $finalHash;
156                 } // END - if
157
158                 // Debug message
159                 //* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: currentFinalHash=' . $this->currentFinalHash);
160
161                 // Get the next raw data chunk from the fragmenter
162                 $rawDataChunk = $fragmenterInstance->getNextRawDataChunk($this->currentFinalHash);
163
164                 // Get chunk hashes and chunk data
165                 $chunkHashes = array_keys($rawDataChunk);
166                 $chunkData   = array_values($rawDataChunk);
167
168                 // Is the required data there?
169                 //* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: chunkHashes[]=' . count($chunkHashes) . ',chunkData[]=' . count($chunkData));
170                 if ((isset($chunkHashes[0])) && (isset($chunkData[0]))) {
171                         // Remember this chunk as queued
172                         $this->queuedChunks[$chunkHashes[0]] = $chunkData[0];
173
174                         // Return the raw data
175                         return $chunkData[0];
176                 } else {
177                         // Return zero string
178                         return '';
179                 }
180         }
181
182         /**
183          * Getter for port number to satify ProtocolHandler
184          *
185          * @return      $port   The port number
186          */
187         public final function getPort () {
188                 return $this->port;
189         }
190
191         /**
192          * Setter for port number to satify ProtocolHandler
193          *
194          * @param       $port   The port number
195          * @return      void
196          */
197         protected final function setPort ($port) {
198                 $this->port = $port;
199         }
200
201         /**
202          * Getter for protocol
203          *
204          * @return      $protocol       Used protocol
205          */
206         public final function getProtocol () {
207                 return $this->protocol;
208         }
209
210         /**
211          * Setter for protocol
212          *
213          * @param       $protocol       Used protocol
214          * @return      void
215          */
216         protected final function setProtocol ($protocol) {
217                 $this->protocol = $protocol;
218         }
219
220         /**
221          * Getter for IP address
222          *
223          * @return      $address        The IP address
224          */
225         public final function getAddress () {
226                 return $this->address;
227         }
228
229         /**
230          * Setter for IP address
231          *
232          * @param       $address        The IP address
233          * @return      void
234          */
235         protected final function setAddress ($address) {
236                 $this->address = $address;
237         }
238
239         /**
240          * "Accept" a visitor by simply calling it back
241          *
242          * @param       $visitorInstance        A Visitor instance
243          * @return      void
244          */
245         protected final function accept (Visitor $visitorInstance) {
246                 // Just call the visitor
247                 $visitorInstance->visitConnectionHelper($this);
248         }
249
250         /**
251          * Sends raw package data to the recipient
252          *
253          * @param       $packageData            Raw package data
254          * @return      $totalSentBytes         Total sent bytes to the peer
255          * @throws      InvalidSocketException  If we got a problem with this socket
256          */
257         public function sendRawPackageData (array $packageData) {
258                 // The helper's state must be 'connected'
259                 $this->getStateInstance()->validatePeerStateConnected();
260
261                 // Cache buffer length
262                 $bufferSize = $this->getConfigInstance()->getConfigEntry($this->getProtocol() . '_buffer_length');
263
264                 // Init variables
265                 $rawData        = '';
266                 $dataStream     = ' ';
267                 $totalSentBytes = 0;
268
269                 // Fill sending buffer with data
270                 while ((strlen($rawData) < $bufferSize) && (strlen($dataStream) > 0)) {
271                         // Convert the package data array to a raw data stream
272                         $dataStream = $this->getRawDataFromPackageArray($packageData);
273                         //* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: Adding ' . strlen($dataStream) . ' bytes to the sending buffer ...');
274                         $rawData .= $dataStream;
275                 } // END - while
276
277                 // Nothing to sent is bad news, so assert on it
278                 assert(strlen($rawData) > 0);
279
280                 // Encode the raw data with our output-stream
281                 $encodedData = $this->getOutputStreamInstance()->streamData($rawData);
282
283                 // Calculate difference
284                 $this->diff = $bufferSize - strlen($encodedData);
285
286                 // Get socket resource
287                 $socketResource = $this->getSocketResource();
288
289                 // Init sent bytes
290                 $sentBytes = 0;
291
292                 // Deliver all data
293                 while ($sentBytes !== false) {
294                         // And deliver it
295                         //* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: Sending out ' . strlen($encodedData) . ' bytes,bufferSize=' . $bufferSize . ',diff=' . $this->diff);
296                         $sentBytes = @socket_write($socketResource, $encodedData, ($bufferSize - $this->diff));
297
298                         // If there was an error, we don't continue here
299                         if ($sentBytes === false) {
300                                 // Handle the error with a faked recipientData array
301                                 $this->handleSocketError($socketResource, array('0.0.0.0', '0'));
302
303                                 // And throw it
304                                 throw new InvalidSocketException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
305                         } elseif (($sentBytes == 0) && (strlen($encodedData) > 0)) {
306                                 // Nothing sent means we are done
307                                 //* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: All sent! (' . __LINE__ . ')');
308                                 break;
309                         }
310
311                         // The difference between sent bytes and length of raw data should not go below zero
312                         assert((strlen($encodedData) - $sentBytes) >= 0);
313
314                         // Add total sent bytes
315                         $totalSentBytes += $sentBytes;
316
317                         // Cut out the last unsent bytes
318                         //* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: Sent out ' . $sentBytes . ' of ' . strlen($encodedData) . ' bytes ...');
319                         $encodedData = substr($encodedData, $sentBytes);
320
321                         // Calculate difference again
322                         $this->diff = $bufferSize - strlen($encodedData);
323
324                         // Can we abort?
325                         if (strlen($encodedData) <= 0) {
326                                 // Abort here, all sent!
327                                 //* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: All sent! (' . __LINE__ . ')');
328                                 break;
329                         } // END - if
330                 } // END - while
331
332                 // Return sent bytes
333                 //* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: totalSentBytes=' . $totalSentBytes);
334                 return $totalSentBytes;
335         }
336
337         /**
338          * Marks this connection as shutted down
339          *
340          * @return      void
341          */
342         protected final function markConnectionShuttedDown () {
343                 /* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: ' . $this->__toString() . ' has been marked as shutted down');
344                 $this->shuttedDown = true;
345         }
346
347         /**
348          * Getter for shuttedDown
349          *
350          * @return      $shuttedDown    Wether this connection is shutted down
351          */
352         public final function isShuttedDown () {
353                 /* NOISY-DEBUG: */ $this->debugOutput('CONNECTION: ' . $this->__toString() . ',shuttedDown=' . intval($this->shuttedDown));
354                 return $this->shuttedDown;
355         }
356
357         // ************************************************************************
358         //                 Socket error handler call-back methods
359         // ************************************************************************
360
361         /**
362          * Handles socket error 'connection timed out', but does not clear it for
363          * later debugging purposes.
364          *
365          * @param       $socketResource         A valid socket resource
366          * @throws      SocketConnectionException       The connection attempts fails with a time-out
367          */
368         private function socketErrorConnectionTimedOutHandler ($socketResource) {
369                 // Get socket error code for verification
370                 $socketError = socket_last_error($socketResource);
371
372                 // Get error message
373                 $errorMessage = socket_strerror($socketError);
374
375                 // Shutdown this socket
376                 $this->shutdownSocket($socketResource);
377
378                 // Throw it again
379                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
380         }
381 }
382
383 // [EOF]
384 ?>