<?php
/**
* Use PHP's socket API to get data from one or more servers. It has such features:
* 1. fine-grined timeout controll
* 2. if given many servers, when cannot connect to one, it will try remainer
* 3. get data in sequence indicated by $continue parameter
* 4. get data end with $delimiter, or in $length bytes, or unknown $length
*
*/
class SocketClient
{
protected $_timeout;
protected $_servers;
protected $_defaultPort = 80;
private $_host;
private $_port;
private $_sock = false;
private $_connected = false;
private $_errMsg = '';
/**
* Construct one socket client
*
* @param array or String $svr
* @param array $timeout
*/
public function __construct($svr, $timeout=array())
{
if (is_string($svr)) $this->_servers[] = array('host'=>$svr);
elseif (isset($svr['host'])) $this->_servers[] = $svr;
else $this->_servers = $svr;
$this->_timeout = array('conn'=>1, 'send'=>1, 'recv'=>1);
if (!empty($timeout)) $this->_timeout = array_merge($this->_timeout, $timeout);
}
/**
* connect to many servers, when cannot connect to one, it will try remainer
*
* @return Boolean, if connected to server then TRUE, other FALSE
*/
protected function sconn()
{
if ($this->_connected) return true;
foreach ($this->_servers as $svr)
{
$this->_host = $svr['host'];
$this->_port = isset($svr['port']) ? intval($svr['port']) : $this->_defaultPort;
if ('' == $this->_host || $this->_port < 0) continue;
$this->_sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (false === $this->_sock) return false;
socket_set_option($this->_sock, SOL_SOCKET, SO_RCVTIMEO, array("sec" => $this->_timeout['recv'], "usec" => 0));
socket_set_option($this->_sock, SOL_SOCKET, SO_SNDTIMEO, array("sec" => $this->_timeout['send'], "usec" => 0));
socket_set_nonblock($this->_sock);
@socket_connect($this->_sock, $this->_host, $this->_port);
$r = $w = $e = array($this->_sock);
if (socket_select($r, $w, $e, $this->_timeout['conn']) == 1)
{
socket_set_block($this->_sock);
$this->_connected = true;
return true;
}
else
{
$this->_errMsg = 'Cannot connect to '.$this->_host.':'.$this->_port.', '.socket_strerror(socket_last_error($this->_sock));
socket_close($this->_sock);
return false;
}
}
return false;
}
/**
* Add servers to Client
*
* @param array $svrs = array(0=>array('host'=>$host1, 'port'=>$port1),...)
*/
public function addServer($svrs)
{
$this->_servers = array_merge($this->_servers, $svrs);
}
/**
* get certain length data from server
*
* @param String $request
* @param Integer $length, 0 indicates unknown length
* @param Boolean $continue
* @return if error happens then FALSE, other will retrun data that fetched from server
*/
public function get($request, $length=0, $continue=false)
{
if ('' != $this->getErrorMessage()) return false;
if (!$this->_sock && !$this->_connected)
{
if (empty($request)) return false;
// connect to server
if (false === $this->sconn()) return false;
// send data to server
if (socket_write($this->_sock, $request) != strlen($request))
{
socket_close($this->_sock);
$this->_errMsg = 'Sending to '.$this->_host.':'.$this->_port.', '.socket_strerror(socket_last_error($this->_sock));
return false;
}
}
// get data from server
if (!is_resource($this->_sock)) return false;
$data = '';
$length = intval($length);
if ($length > 0)
{
$size = socket_recv($this->_sock, $data, $length, 0x100);
if ($size !== $length)
{
socket_close($this->_sock);
$this->_errMsg ='Receving from '.$this->_host.':'.$this->_port.', '.socket_strerror(socket_last_error($this->_sock));
return false;
}
}
else
{
$trySize = 16384;
for(;;)
{
$tail = '';
$size = socket_recv($this->_sock, $tail, $trySize, 0x100);
if (false === $size)
{
$this->_errMsg ='Receving from '.$this->_host.':'.$this->_port.', '.socket_strerror(socket_last_error($this->_sock));
socket_close($this->_sock);
return false;
}
$data .= $tail;
if ($size < $trySize) break;
}
}
if (!$continue) socket_close($this->_sock);
return $data;
}
/**
* get one line end with $delimiter
*
* @param String $request
* @param String $delimiter
* @param Boolean $continue
* @return if error happens then FALSE, other will retrun data that fetched from server
*/
public function getLine($request, $delimiter="\r\n", $continue=false)
{
if ('' != $this->getErrorMessage()) return false;
if (!$this->_sock && !$this->_connected)
{
if (empty($request)) return false;
// connect to server
if (false === $this->sconn()) return false;
// send data to server
if (socket_write($this->_sock, $request) != strlen($request))
{
socket_close($this->_sock);
$this->_errMsg = 'Sending to '.$this->_host.':'.$this->_port.', '.socket_strerror(socket_last_error($this->_sock));
return false;
}
}
// get data from server
if (!is_resource($this->_sock)) return false;
$header = '';
$pos = 0;
$maxpos = strlen($delimiter) - 1;
for (;;)
{
$ch = '';
$size = socket_recv($this->_sock, $ch, 1, 0x100);
if ($size === false)
{
socket_close($this->_sock);
$this->_errMsg ='Receving from '.$this->_host.':'.$this->_port.', '.socket_strerror(socket_last_error($this->_sock));
return false;
}
$header .= $ch;
if ($ch == $delimiter{$pos})
{
if ($pos == $maxpos) break;
$pos ++;
}
else if ($pos > 0)
{
$pos = 0;
}
}
if (!$continue) socket_close($this->_sock);
return $header;
}
public function getErrorMessage()
{
return $this->_errMsg;
}
}