00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00016 final class Socket
00017 {
00018 const DEFAULT_TIMEOUT = 1000;
00019
00020 const EAGAIN = 11;
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
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
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
00143
00144
00145
00146 socket_set_nonblock($this->socket);
00147
00148 try {
00149 socket_connect($this->socket, $this->host, $this->port);
00150 } catch (BaseException $e) {
00151
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
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
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
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
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;
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
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 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 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 ?>