# Socket.class.php

Go to the documentation of this file.

    00001 <?php
    00002 /***************************************************************************
    00003  *   Copyright (C) 2007 by Ivan Y. Khvostishkov                            *
    00004  *                                                                         *
    00005  *   This program is free software; you can redistribute it and/or modify  *
    00006  *   it under the terms of the GNU Lesser General Public License as        *
    00007  *   published by the Free Software Foundation; either version 3 of the    *
    00008  *   License, or (at your option) any later version.                       *
    00009  *                                                                         *
    00010  ***************************************************************************/
    00011
    00015     final class Socket
    00016     {
    00017         const DEFAULT_TIMEOUT   = 1000; // milliseconds
    00018
    00019         const EAGAIN            = 11;   // timeout, try again
    00020
    00021         private $socket     = null;
    00022         private $connected  = false;
    00023
    00024         private $host       = null;
    00025         private $port       = null;
    00026
    00027         private $inputStream    = null;
    00028         private $outputStream   = null;
    00029
    00030         private $closed         = false;
    00031         private $inputShutdown  = false;
    00032         private $outputShutdown = false;
    00033
    00034         // milliseconds
    00035         private $readTimeout    = null;
    00036         private $writeTimeout   = null;
    00037
    00038         public function __construct()
    00039         {
    00040             $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    00041
    00042             if ($this->socket === false)
    00043                 throw new NetworkException(
    00044                     'socket creating failed: '
    00045                     .socket_strerror(socket_last_error())
    00046                 );
    00047
    00048             $this->inputStream = new SocketInputStream($this);
    00049
    00050             $this->outputStream = new SocketOutputStream($this);
    00051         }
    00052
    00053         public function __destruct()
    00054         {
    00055             if (!$this->closed) {
    00056                 try {
    00057                     $this->close();
    00058                 } catch (BaseException $e) {
    00059                     /* boo! */
    00060                 }
    00061             }
    00062         }
    00063
    00067         public static function create()
    00068         {
    00069             return new self;
    00070         }
    00071
    00075         public function setHost($host)
    00076         {
    00077             Assert::isNull($this->host);
    00078
    00079             $this->host = $host;
    00080
    00081             return $this;
    00082         }
    00083
    00084         public function getHost()
    00085         {
    00086             return $this->host;
    00087         }
    00088
    00092         public function setPort($port)
    00093         {
    00094             Assert::isNull($this->port);
    00095
    00096             $this->port = $port;
    00097
    00098             return $this;
    00099         }
    00100
    00101         public function getPort()
    00102         {
    00103             return $this->port;
    00104         }
    00105
    00106         public function isConnected()
    00107         {
    00108             return $this->connected;
    00109         }
    00110
    00114         public function getInputStream()
    00115         {
    00116             $this->checkRead();
    00117
    00118             return $this->inputStream;
    00119         }
    00120
    00124         public function getOutputStream()
    00125         {
    00126             $this->checkWrite();
    00127
    00128             return $this->outputStream;
    00129         }
    00130
    00134         public function connect($connectTimeout = self::DEFAULT_TIMEOUT)
    00135         {
    00136             Assert::isTrue(
    00137                 isset($this->host) && isset($this->port),
    00138                 'set host and port first'
    00139             );
    00140
    00141             // TODO: assuming we are in blocking mode
    00142             // for non-blocking mode this method must throw an exception,
    00143             // use non-blocking socket channels instead
    00144
    00145             socket_set_nonblock($this->socket);
    00146
    00147             try {
    00148                 socket_connect($this->socket, $this->host, $this->port);
    00149             } catch (BaseException $e) {
    00150                 /* yum-yum */
    00151             }
    00152
    00153             socket_set_block($this->socket);
    00154
    00155             $r = array($this->socket);
    00156             $w = array($this->socket);
    00157             $e = array($this->socket);
    00158
    00159             switch (
    00160                 socket_select(
    00161                     $r, $w, $e,
    00162                     self::getSeconds($connectTimeout),
    00163                     self::getMicroseconds($connectTimeout)
    00164                 )
    00165             ) {
    00166                 case 0:
    00167                     throw new NetworkException(
    00168                         "unable to connect to '{$this->host}:{$this->port}': "
    00169                         ."connection timed out"
    00170                     );
    00171
    00172                 case 1:
    00173                     $this->connected = true;
    00174                     break;
    00175
    00176                 case 2:
    00177                     // yanetut
    00178                     throw new NetworkException(
    00179                         "unable to connect to '{$this->host}:{$this->port}': "
    00180                         .'connection refused'
    00181                     );
    00182             }
    00183
    00184             if (!$this->readTimeout)
    00185                 $this->setReadTimeout(self::DEFAULT_TIMEOUT);
    00186
    00187             if (!$this->writeTimeout)
    00188                 $this->setWriteTimeout(self::DEFAULT_TIMEOUT);
    00189
    00190             return $this;
    00191         }
    00192
    00196         public function setReadTimeout($timeout)
    00197         {
    00198             $timeVal = array(
    00199                 'sec' => self::getSeconds($timeout),
    00200                 'usec' => self::getMicroseconds($timeout)
    00201             );
    00202
    00203             socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, $timeVal);
    00204
    00205             $this->readTimeout = $timeout;
    00206
    00207             return $this;
    00208         }
    00209
    00213         public function setWriteTimeout($timeout)
    00214         {
    00215             $timeVal = array(
    00216                 'sec' => self::getSeconds($timeout),
    00217                 'usec' => self::getMicroseconds($timeout)
    00218             );
    00219
    00220             socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, $timeVal);
    00221
    00222             $this->readTimeout = $timeout;
    00223
    00224             return $this;
    00225         }
    00226
    00230         public function setTimeout($timeout)
    00231         {
    00232             $this->setReadTimeout($timeout);
    00233             $this->setWriteTimeout($timeout);
    00234
    00235             return $this;
    00236         }
    00237
    00238         // NOTE: return value may slightly differ from $this->readTimeout
    00239         public function getReadTimeout()
    00240         {
    00241             $timeVal = socket_get_option($this->socket, SOL_SOCKET, SO_RCVTIMEO);
    00242
    00243             return $timeVal['sec'] * 1000 + (int) ($timeVal['usec'] / 1000);
    00244         }
    00245
    00246         //  return value may slightly differ from $this->writeTimeout
    00247         public function getWriteTimeout()
    00248         {
    00249             $timeVal = socket_get_option($this->socket, SOL_SOCKET, SO_RCVTIMEO);
    00250
    00251             return $timeVal['sec'] * 1000 + (int) ($timeVal['usec'] / 1000);
    00252         }
    00253
    00257         public function read($length)
    00258         {
    00259             $this->checkRead();
    00260
    00261             socket_clear_error($this->socket);
    00262
    00263             try {
    00264                 $result = socket_read($this->socket, $length);
    00265             } catch (BaseException $e) {
    00266                 // probably connection reset by peer
    00267                 $result = false;
    00268             }
    00269
    00270             if ($result === false && !$this->isTimedOut())
    00271                 throw new NetworkException(
    00272                     'socket reading failed: '
    00273                     .socket_strerror(socket_last_error())
    00274                 );
    00275             elseif ($result === '')
    00276                 return null; // eof
    00277
    00278             return $result;
    00279         }
    00280
    00284         public function write($buffer, $length = null)
    00285         {
    00286             $this->checkWrite();
    00287
    00288             socket_clear_error($this->socket);
    00289
    00290             try {
    00291                 if ($length === null)
    00292                     $result = socket_write($this->socket, $buffer);
    00293                 else
    00294                     $result = socket_write($this->socket, $buffer, $length);
    00295
    00296             } catch (BaseException $e) {
    00297                 // probably connection reset by peer
    00298                 $result = false;
    00299             }
    00300
    00301             if ($result === false && !$this->isTimedOut())
    00302                 throw new NetworkException(
    00303                     'socket writing failed: '
    00304                     .socket_strerror(socket_last_error())
    00305                 );
    00306
    00307             return $result;
    00308         }
    00309
    00310         public function isTimedOut()
    00311         {
    00312             return (socket_last_error($this->socket) === self::EAGAIN);
    00313         }
    00314
    00318         public function shutdownInput()
    00319         {
    00320             socket_shutdown($this->socket, 0);
    00321
    00322             $this->inputShutdown = true;
    00323
    00324             return $this;
    00325         }
    00326
    00330         public function shutdownOutput()
    00331         {
    00332             socket_shutdown($this->socket, 1);
    00333
    00334             $this->outputShutdown = true;
    00335
    00336             return $this;
    00337         }
    00338
    00342         public function close()
    00343         {
    00344             if (!$this->inputShutdown)
    00345                 $this->shutdownInput();
    00346
    00347             if (!$this->outputShutdown)
    00348                 $this->shutdownOutput();
    00349
    00350             socket_close($this->socket);
    00351
    00352             $this->closed = true;
    00353
    00354             return $this;
    00355         }
    00356
    00357         private static function getSeconds($timeout)
    00358         {
    00359             return (int) ($timeout / 1000);
    00360         }
    00361
    00362         private static function getMicroseconds($timeout)
    00363         {
    00364             return (int) ($timeout % 1000 * 1000);
    00365         }
    00366
    00367         /* void */ private function checkRead()
    00368         {
    00369             if ($this->closed || !$this->connected || $this->inputShutdown)
    00370                 throw new NetworkException(
    00371                     'cannod read from socket: '
    00372                     .'it is closed, not connected, or has been shutdown'
    00373                 );
    00374         }
    00375
    00376         /* void */ private function checkWrite()
    00377         {
    00378             if ($this->closed || !$this->connected || $this->inputShutdown)
    00379                 throw new NetworkException(
    00380                     'cannod write to socket: '
    00381                     .'it is closed, not connected, or has been shutdown'
    00382                 );
    00383         }
    00384     }
    00385 ?>