mxmbsocket/lib/Ratchet/Socket.php
Chris Boden 1b01582ab9 SocketInterface
Added unix socket methods to interface, replaced __call/call_user_func calls with concrete methods
2011-11-20 20:38:20 -05:00

252 lines
7.8 KiB
PHP

<?php
namespace Ratchet;
use Ratchet\Application\ProtocolInterface;
/**
* A wrapper for the PHP socket_ functions
* @author Chris Boden <shout at chrisboden dot ca>
* @link http://ca2.php.net/manual/en/book.sockets.php
* @todo Possibly move this into Ratchet\Resource - another concrete could use streams
*/
class Socket implements SocketInterface {
/**
* @type resource
*/
protected $_resource;
public static $_defaults = array(
'domain' => AF_INET
, 'type' => SOCK_STREAM
, 'protocol' => SOL_TCP
);
/**
* @param int Specifies the protocol family to be used by the socket.
* @param int The type of communication to be used by the socket
* @param int Sets the specific protocol within the specified domain to be used when communicating on the returned socket
* @throws Ratchet\Exception
*/
public function __construct($domain = null, $type = null, $protocol = null) {
list($domain, $type, $protocol) = static::getConfig($domain, $type, $protocol);
$this->_resource = @socket_create($domain, $type, $protocol);
if (!is_resource($this->_resource)) {
throw new Exception($this);
}
}
public function __destruct() {
@socket_close($this->_resource);
}
public function __toString() {
$id = (string)$this->getResource();
return (string)substr($id, strrpos($id, '#') + 1);
}
/**
* @return resource (Socket)
*/
public function getResource() {
return $this->_resource;
}
public function __clone() {
$this->_resource = @socket_accept($this->_resource);
if (false === $this->_resource) {
throw new Exception($this);
}
}
public function deliver($message) {
$len = strlen($message);
do {
$sent = $this->write($message, 4);
$len -= $sent;
$message = substr($message, $sent);
} while ($len > 0);
}
public function bind($address, $port = 0) {
if (false === @socket_bind($this->getResource(), $address, $port)) {
throw new Exception;
}
return $this;
}
public function close() {
@socket_close($this->getResource());
unset($this->_resource);
}
public function connect($address, $port = 0) {
if (false === @socket_connect($this->getResource(), $address, $port)) {
throw new Exception;
}
return $this;
}
public function get_option($level, $optname) {
if (false === ($res = @socket_get_option($this->getResource(), $level, $optname))) {
throw new Exception;
}
return $res;
}
public function listen($backlog = 0) {
if (false === @socket_listen($this->getResource(), $backlog)) {
throw new Exception;
}
return $this;
}
/**
* @see http://ca3.php.net/manual/en/function.socket-recv.php
* @param string Variable to write data to
* @param int Number of bytes to read
* @param int
* @return int Number of bytes received
* @throws Exception
*/
public function recv(&$buf, $len, $flags) {
if (false === ($bytes = @socket_recv($this->_resource, $buf, $len, $flags))) {
throw new Exception($this);
}
return $bytes;
}
/**
* Since PHP is retarded and their golden hammer, the array, doesn't implement any interfaces I have to hackishly overload socket_select
* @see http://ca3.php.net/manual/en/function.socket-select.php
* @param Iterator|array|NULL The sockets listed in the read array will be watched to see if characters become available for reading (more precisely, to see if a read will not block - in particular, a socket resource is also ready on end-of-file, in which case a socket_read() will return a zero length string).
* @param Iterator|array|NULL The sockets listed in the write array will be watched to see if a write will not block.
* @param Iterator|array|NULL The sockets listed in the except array will be watched for exceptions.
* @param int The tv_sec and tv_usec together form the timeout parameter. The timeout is an upper bound on the amount of time elapsed before socket_select() return. tv_sec may be zero , causing socket_select() to return immediately. This is useful for polling. If tv_sec is NULL (no timeout), socket_select() can block indefinitely.
* @param int
* @throws \InvalidArgumentException
* @throws Exception
*/
public function select(&$read, &$write, &$except, $tv_sec, $tv_usec = 0) {
$read = static::mungForSelect($read);
$write = static::mungForSelect($write);
$except = static::mungForSelect($except);
$num = socket_select($read, $write, $except, $tv_sec, $tv_usec);
if (false === $num) {
throw new Exception($this);
}
return $num;
}
public function set_block() {
if (false === @socket_set_block($this->getResource())) {
throw new Exception;
}
return $this;
}
public function set_nonblock() {
if (false === @socket_set_nonblock($this->getResource())) {
throw new Exception;
}
return $this;
}
public function set_option($level, $optname, $optval) {
if (false === @socket_set_option($this->getResource(), $level, $optname, $optval)) {
throw new Exception;
}
return $this;
}
public function shutdown($how = 2) {
if (false === @socket_shutdown($this->getResource(), $how)) {
throw new Exception;
}
return $this;
}
public function write($buffer, $length = 0) {
if (false === ($res = @socket_write($this->getResource(), $buffer, $length))) {
throw new Exception;
}
return $res;
}
/**
* @param Ratchet\Application\ProtocolInterface
* @return Socket
* @throws Exception
*/
public static function createFromConfig(ProtocolInterface $protocol) {
$config = $protocol::getDefaultConfig();
$class = get_called_class();
$socket = new $class($config['domain'] ?: null, $config['type'] ?: null, $config['protocol'] ?: null);
if (is_array($config['options'])) {
foreach ($config['options'] as $level => $pair) {
foreach ($pair as $optname => $optval) {
$socket->set_option($level, $optname, $optval);
}
}
}
return $socket;
}
/**
* @internal
* @param int Specifies the protocol family to be used by the socket.
* @param int The type of communication to be used by the socket
* @param int Sets the specific protocol within the specified domain to be used when communicating on the returned socket
* @return array
*/
protected static function getConfig($domain = null, $type = null, $protocol = null) {
foreach (static::$_defaults as $key => $val) {
if (null === $$key) {
$$key = $val;
}
}
return array($domain, $type, $protocol);
}
/**
* @internal
* @param Iterator|array|NULL
* @return array|NULL
* @throws \InvalidArgumentException
*/
protected static function mungForSelect($collection) {
if (null === $collection || is_array($collection)) {
return $collection;
}
if (!($collection instanceof \Traversable)) {
throw new \InvalidArgumentException('Object pass is not traversable');
}
$return = array();
foreach ($collection as $key => $socket) {
$return[$key] = ($socket instanceof \Ratchet\Socket ? $socket->getResource() : $socket);
}
return $return;
}
}