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

Generated on Sun Dec 9 21:56:24 2007 for onPHP by  doxygen 1.5.4