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 ?>