Add tcp client and tcp server support
git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@4014 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
This commit is contained in:
72
Tests/tcpclientTest.php
Normal file
72
Tests/tcpclientTest.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/** Test the TCP client */
|
||||||
|
class test_tcpclient extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function test_GoogleIPv4 ()
|
||||||
|
{
|
||||||
|
$tcpclient = new \tcpclient ("www.google.fr", 80);
|
||||||
|
$tcpclient->preferIPv4 (true);
|
||||||
|
$tcpclient->connect ();
|
||||||
|
$tcpclient->send ("GET / HTTP/1.1\r\n".
|
||||||
|
"Host: www.google.fr\r\n".
|
||||||
|
"User-Agent: DomFramework\r\n".
|
||||||
|
"Accept: *"."/*\r\n".
|
||||||
|
"\r\n");
|
||||||
|
$res = "";
|
||||||
|
while (($read = $tcpclient->read ()) !== "")
|
||||||
|
$res .= $read."\r\n";
|
||||||
|
$tcpclient->disconnect ();
|
||||||
|
$this->assertSame (substr ($res, 0, 15), "HTTP/1.1 200 OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_GoogleIPv4orIpv6 ()
|
||||||
|
{
|
||||||
|
$tcpclient = new \tcpclient ("www.google.fr", 80);
|
||||||
|
$tcpclient->connect ();
|
||||||
|
$tcpclient->send ("GET / HTTP/1.1\r\n".
|
||||||
|
"Host: www.google.fr\r\n".
|
||||||
|
"User-Agent: DomFramework\r\n".
|
||||||
|
"Accept: *"."/*\r\n".
|
||||||
|
"\r\n");
|
||||||
|
$res = "";
|
||||||
|
while (($read = $tcpclient->read ()) !== "")
|
||||||
|
$res .= $read."\r\n";
|
||||||
|
$tcpclient->disconnect ();
|
||||||
|
$this->assertSame (substr ($res, 0, 15), "HTTP/1.1 200 OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_GoogleSSL ()
|
||||||
|
{
|
||||||
|
$tcpclient = new \tcpclient ("www.google.fr", 443);
|
||||||
|
$tcpclient->connect ();
|
||||||
|
$tcpclient->cryptoEnable (true);
|
||||||
|
$tcpclient->send ("GET / HTTP/1.1\r\n".
|
||||||
|
"Host: www.google.fr\r\n".
|
||||||
|
"User-Agent: DomFramework\r\n".
|
||||||
|
"Accept: */*\r\n".
|
||||||
|
"\r\n");
|
||||||
|
$res = "";
|
||||||
|
while (($read = $tcpclient->read ()) !== "")
|
||||||
|
$res .= $read."\r\n";
|
||||||
|
$tcpclient->disconnect ();
|
||||||
|
$this->assertSame (substr ($res, 0, 15), "HTTP/1.1 200 OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_GoogleSSLIPv6 ()
|
||||||
|
{
|
||||||
|
$tcpclient = new \tcpclient ("ipv6.google.com", 443);
|
||||||
|
$tcpclient->connect ();
|
||||||
|
$tcpclient->cryptoEnable (true);
|
||||||
|
$tcpclient->send ("GET / HTTP/1.1\r\n".
|
||||||
|
"Host: www.google.fr\r\n".
|
||||||
|
"User-Agent: DomFramework\r\n".
|
||||||
|
"Accept: */*\r\n".
|
||||||
|
"\r\n");
|
||||||
|
$res = "";
|
||||||
|
while (($read = $tcpclient->read ()) !== "")
|
||||||
|
$res .= $read."\r\n";
|
||||||
|
$tcpclient->disconnect ();
|
||||||
|
$this->assertSame (substr ($res, 0, 15), "HTTP/1.1 200 OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
249
tcpclient.php
Normal file
249
tcpclient.php
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<?php
|
||||||
|
/** TCP Client
|
||||||
|
* Allow to create TCP connections to a server.
|
||||||
|
* If both IPv6 and IPv4 are allowed by the server, try in IPv6 then back in
|
||||||
|
* IPv4 if it doesn't works.
|
||||||
|
* If the name of the server is provided instead of the IP, look for all the
|
||||||
|
* IP address, manage the CNAME aliases.
|
||||||
|
* If multiple addresses are available in IPv6 or IPv4, randomize them to allow
|
||||||
|
* round-robin connections to the server.
|
||||||
|
* Manage the timeout, send a command, receive a max number of bytes,
|
||||||
|
* allow SSL trafic with CA verification
|
||||||
|
*/
|
||||||
|
class tcpclient
|
||||||
|
{
|
||||||
|
/** The IPv6 allowed for the server
|
||||||
|
*/
|
||||||
|
private $ipv6 = array ();
|
||||||
|
|
||||||
|
/** The IPv4 allowed for the server
|
||||||
|
*/
|
||||||
|
private $ipv4 = array ();
|
||||||
|
|
||||||
|
/** The ipOrName parameter
|
||||||
|
*/
|
||||||
|
private $ipOrName = null;
|
||||||
|
|
||||||
|
/** The port to connect to the server
|
||||||
|
*/
|
||||||
|
private $port = null;
|
||||||
|
|
||||||
|
/** Prefer the IPv4 connection either the IPv6 is valid
|
||||||
|
*/
|
||||||
|
private $preferIPv4 = false;
|
||||||
|
|
||||||
|
/** The internal socket connected to the server
|
||||||
|
*/
|
||||||
|
private $socket = null;
|
||||||
|
|
||||||
|
/** Read in Text mode or in Binary mode
|
||||||
|
*/
|
||||||
|
private $readMode = "text";
|
||||||
|
|
||||||
|
/** Initialize the object, by setting the name or the IP of the server
|
||||||
|
* @param string $ipOrName The IP or the name of the server
|
||||||
|
* @param integer $port The port of the server to connect
|
||||||
|
*/
|
||||||
|
public function __construct ($ipOrName, $port)
|
||||||
|
{
|
||||||
|
if (filter_var ($ipOrName, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
||||||
|
$this->ipv4 = array ($ipOrName);
|
||||||
|
elseif (filter_var ($ipOrName, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
||||||
|
$this->ipv6 = array ($ipOrName);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$i = 0;
|
||||||
|
while (empty ($this->ipv4) && empty ($this->ipv6) && $i < 10)
|
||||||
|
{
|
||||||
|
$nsRecords = dns_get_record ($ipOrName, DNS_AAAA + DNS_A + DNS_CNAME);
|
||||||
|
foreach ($nsRecords as $val)
|
||||||
|
{
|
||||||
|
if ($val["type"] === "CNAME")
|
||||||
|
$ipOrName = $val["target"];
|
||||||
|
elseif ($val["type"] === "A")
|
||||||
|
$this->ipv4[] = $val["ip"];
|
||||||
|
elseif ($val["type"] === "AAAA")
|
||||||
|
$this->ipv6[] = $val["ipv6"];
|
||||||
|
}
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
if ($i >= 10)
|
||||||
|
throw new \Exception ("Can not find the IP for $ipOrName : ".
|
||||||
|
"CNAME loop", 500);
|
||||||
|
if (empty ($this->ipv4) && empty ($this->ipv6))
|
||||||
|
throw new \Exception ("Can not find the IP for $ipOrName : ".
|
||||||
|
"No A or AAAA record", 500);
|
||||||
|
}
|
||||||
|
$port = intval ($port);
|
||||||
|
if ($port < 0 || $port > 65535)
|
||||||
|
throw new \Exception ("Invalid port provided to connection to server",
|
||||||
|
500);
|
||||||
|
$this->ipOrName = $ipOrName;
|
||||||
|
$this->port = $port;
|
||||||
|
shuffle ($this->ipv6);
|
||||||
|
shuffle ($this->ipv4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set/get the preferIPv4 property
|
||||||
|
* @param boolean|null $preferIPv4 The preferIPv4 property to set (or to get
|
||||||
|
* if null)
|
||||||
|
*/
|
||||||
|
public function preferIPv4 ($preferIPv4 = null)
|
||||||
|
{
|
||||||
|
if ($preferIPv4 === null)
|
||||||
|
return $this->preferIPv4;
|
||||||
|
if ($this->socket !== null)
|
||||||
|
throw new \Exception ("Can not connect in IPv4 prefered to server ".
|
||||||
|
" $this->ipOrName : The server is already connected", 500);
|
||||||
|
$this->preferIPv4 = !!$preferIPv4;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set/get the read mode : text or binary
|
||||||
|
* @param string|null $readMode The mode to set (or get if null)
|
||||||
|
*/
|
||||||
|
public function readMode ($readMode = null)
|
||||||
|
{
|
||||||
|
if ($readMode === null)
|
||||||
|
return $this->readMode;
|
||||||
|
if ($readMode !== "text" && $readMode !== "binary")
|
||||||
|
throw new \Exception ("Invalid readMode provided (nor text nor binary)",
|
||||||
|
500);
|
||||||
|
$this->readMode = $readMode;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialize the connection to the server
|
||||||
|
* Return the socket
|
||||||
|
*/
|
||||||
|
public function connect ()
|
||||||
|
{
|
||||||
|
if ($this->preferIPv4)
|
||||||
|
$ips = array_merge ($this->ipv4, $this->ipv6);
|
||||||
|
else
|
||||||
|
$ips = array_merge ($this->ipv6, $this->ipv4);
|
||||||
|
foreach ($ips as $ip)
|
||||||
|
{
|
||||||
|
if (filter_var ($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
||||||
|
$ip = "[$ip]";
|
||||||
|
$socket = stream_socket_client ("tcp://$ip:$this->port", $errno, $errstr);
|
||||||
|
if ($socket === false)
|
||||||
|
continue;
|
||||||
|
$this->socket = $socket;
|
||||||
|
return $this->socket;
|
||||||
|
}
|
||||||
|
throw new \Exception ("Can not connect to server $this->ipOrName : ".
|
||||||
|
$errstr, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Activate the SSL connection.
|
||||||
|
* Put the socket in blocking mode, as it is mandatory to have SSL connection
|
||||||
|
* @param boolean $val True to activate, false to disable SSL
|
||||||
|
* @param integer $cryptoMethod The cryptoMethod allowed
|
||||||
|
* @return false if the client can not found a encryption method with the
|
||||||
|
* server
|
||||||
|
*/
|
||||||
|
public function cryptoEnable ($val,
|
||||||
|
$cryptoMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT)
|
||||||
|
{
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not send to server $this->ipOrName : ".
|
||||||
|
"The server is not connected", 500);
|
||||||
|
// Setting the options allow the IP to be decided by the connect and valid
|
||||||
|
// the certificate of the server by the name
|
||||||
|
$options = array ("ssl" => array (
|
||||||
|
"peer_name" => $this->ipOrName,
|
||||||
|
// PHP doesn't supports *.google.com, so I need to disable the peer name
|
||||||
|
// verification. Error is :
|
||||||
|
// Peer certificate CN=`*.google.com' did not match expected
|
||||||
|
// CN=`ipv6.l.google.com'
|
||||||
|
"verify_peer_name" => false,
|
||||||
|
"SNI_enabled" => true,
|
||||||
|
));
|
||||||
|
stream_set_blocking ($this->socket, true);
|
||||||
|
stream_context_set_option ($this->socket, $options);
|
||||||
|
return stream_socket_enable_crypto ($this->socket, !!$val, $cryptoMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send a data to the server.
|
||||||
|
* The connection must be established
|
||||||
|
* @param mixed $data The data to send
|
||||||
|
*/
|
||||||
|
public function send ($data)
|
||||||
|
{
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not send to server $this->ipOrName : ".
|
||||||
|
"The server is not connected", 500);
|
||||||
|
$length = strlen ($data);
|
||||||
|
$sentLen = @fwrite ($this->socket, $data);
|
||||||
|
if ($sentLen < $length)
|
||||||
|
throw new \Exception ("Can not send to server $this->ipOrName", 500);
|
||||||
|
return $sentLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read the data from the server.
|
||||||
|
* The connection must be established
|
||||||
|
* Use the readMode in text or binary (text by default)
|
||||||
|
* In text mode, the read return when found the first \r\n, and doesn't
|
||||||
|
* returns the \r\n.
|
||||||
|
* @param integer $maxLength Limit the length of the data from the server
|
||||||
|
* @return The content
|
||||||
|
*/
|
||||||
|
public function read ($maxLength = 1024)
|
||||||
|
{
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not read from server $this->ipOrName : ".
|
||||||
|
"The server is not connected", 500);
|
||||||
|
if ($this->readMode === "text")
|
||||||
|
{
|
||||||
|
$read = stream_get_line ($this->socket, $maxLength, "\r\n");
|
||||||
|
if ($read === false)
|
||||||
|
throw new \Exception ("Can not read from server", 500);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$read = @fread ($this->socket, $maxLength);
|
||||||
|
if ($read === false)
|
||||||
|
throw new \Exception ("Can not read from server" , 500);
|
||||||
|
}
|
||||||
|
return $read;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disconnect the socket
|
||||||
|
*/
|
||||||
|
public function disconnect ()
|
||||||
|
{
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not disconnect server $this->ipOrName : ".
|
||||||
|
"The server is not connected", 500);
|
||||||
|
@stream_socket_shutdown ($this->socket, STREAM_SHUT_RDWR);
|
||||||
|
$this->socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the connection peer address, peer port and localaddress and localport
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getInfo ()
|
||||||
|
{
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not getInfo for server $this->ipOrName : ".
|
||||||
|
"The server is not connected", 500);
|
||||||
|
$name = stream_socket_get_name ($this->socket, false);
|
||||||
|
$pos = strrpos ($name, ":");
|
||||||
|
$localPort = substr ($name, $pos+1);
|
||||||
|
$localAddress = substr ($name, 0, $pos);
|
||||||
|
$name = stream_socket_get_name ($this->socket, true);
|
||||||
|
$pos = strrpos ($name, ":");
|
||||||
|
$port = substr ($name, $pos+1);
|
||||||
|
$address = substr ($name, 0, $pos);
|
||||||
|
return array ($address, $port, $localAddress, $localPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the socket to direct access
|
||||||
|
* @return resource The socket with the client
|
||||||
|
*/
|
||||||
|
public function getSock ()
|
||||||
|
{
|
||||||
|
return $this->socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
301
tcpserver.php
Normal file
301
tcpserver.php
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
<?php
|
||||||
|
/** This class allow to start a TCP server and call a function each time a
|
||||||
|
* client is connected on it. Each client is separated in a child, so the
|
||||||
|
* server allow to have multiple simultaneous connections.
|
||||||
|
*
|
||||||
|
* The handler method or function will get one parameter : the client from
|
||||||
|
* socket_accept. It will allow to use socket_getpeername to get the peer
|
||||||
|
* address and port, socket_read to get the data from the client,
|
||||||
|
* socket_write to write on the client, socket_shutdown ($client) and
|
||||||
|
* socket_close ($client) to finish the connection
|
||||||
|
*
|
||||||
|
* The server has a child limit set to 500 connections by default
|
||||||
|
*/
|
||||||
|
class tcpserver
|
||||||
|
{
|
||||||
|
/** Store the data concerning the sockets and the handlers
|
||||||
|
*/
|
||||||
|
private $handlers = array ();
|
||||||
|
/** Store the addresses
|
||||||
|
*/
|
||||||
|
private $addresses = array ();
|
||||||
|
/** Store the ports
|
||||||
|
*/
|
||||||
|
private $ports = array ();
|
||||||
|
|
||||||
|
/** The max number of children. The maximum of concurrent connections
|
||||||
|
*/
|
||||||
|
private $maxChild = 500;
|
||||||
|
|
||||||
|
/** Set to true in parent, and false in child
|
||||||
|
*/
|
||||||
|
private $parent = true;
|
||||||
|
|
||||||
|
/** The socket in the child
|
||||||
|
*/
|
||||||
|
private $socket = null;
|
||||||
|
|
||||||
|
/** Read in Text mode or in Binary mode
|
||||||
|
*/
|
||||||
|
private $readMode = "text";
|
||||||
|
|
||||||
|
/** Set/get the max children, the maximum of concurrent connections
|
||||||
|
* @param integer|null $val The number of child to get/set
|
||||||
|
*/
|
||||||
|
public function maxChild ($val = null)
|
||||||
|
{
|
||||||
|
if ($val === null)
|
||||||
|
return $this->maxChild;
|
||||||
|
$this->maxChild = intval ($val);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set/get the read mode : text or binary
|
||||||
|
* @param string|null $readMode The mode to set (or get if null)
|
||||||
|
*/
|
||||||
|
public function readMode ($readMode = null)
|
||||||
|
{
|
||||||
|
if ($readMode === null)
|
||||||
|
return $this->readMode;
|
||||||
|
if ($readMode !== "text" && $readMode !== "binary")
|
||||||
|
throw new \Exception ("Invalid readMode provided (nor text nor binary)",
|
||||||
|
500);
|
||||||
|
$this->readMode = $readMode;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the address, port and handler that will be enabled by loop
|
||||||
|
* @param string $address The server address (can be 0.0.0.0 for all IPv4
|
||||||
|
* interfaces or :: for all the IPv4 and IPv6 interfaces)
|
||||||
|
* @param integer $port The port to listen
|
||||||
|
* @param callable $handler The handler that will be called when a client is
|
||||||
|
* connected to the address:port
|
||||||
|
*/
|
||||||
|
public function init ($address, $port, $handler)
|
||||||
|
{
|
||||||
|
$this->handlers["$address:$port"] = $handler;
|
||||||
|
$this->addresses[] = $address;
|
||||||
|
$this->ports[] = $port;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Start the main loop after the init
|
||||||
|
*/
|
||||||
|
public function loop ()
|
||||||
|
{
|
||||||
|
foreach ($this->addresses as $key => $address)
|
||||||
|
{
|
||||||
|
$port = $this->ports[$key];
|
||||||
|
if (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
||||||
|
$sockServer[$key] = stream_socket_server ("tcp://$address:$port",
|
||||||
|
$errno, $errstr);
|
||||||
|
elseif (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
||||||
|
$sockServer[$key] = stream_socket_server ("tcp://[$address]:$port",
|
||||||
|
$errno, $errstr);
|
||||||
|
else
|
||||||
|
throw new \Exception ("Can't create socket : invalid address provided");
|
||||||
|
if ($sockServer[$key] === false)
|
||||||
|
{
|
||||||
|
throw new \Exception ("Can't create socket : $errstr");
|
||||||
|
}
|
||||||
|
//printf ("Listening on %s:%d...\n", $address, $port);
|
||||||
|
}
|
||||||
|
$nbChild = 1;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// Watch if a child process is zombie, then destroy it
|
||||||
|
if (pcntl_wait ($status, WNOHANG) > 0)
|
||||||
|
$nbChild--;
|
||||||
|
$read = $sockServer;
|
||||||
|
$write = null;
|
||||||
|
$except = null;
|
||||||
|
if (stream_select ($read, $write, $except, 0, 200000) < 1)
|
||||||
|
continue;
|
||||||
|
foreach ($read as $sock)
|
||||||
|
{
|
||||||
|
$client = stream_socket_accept ($sock);
|
||||||
|
$name = stream_socket_get_name ($client, false);
|
||||||
|
$pos = strrpos ($name, ":");
|
||||||
|
$localPort = substr ($name, $pos+1);
|
||||||
|
$localAddress = substr ($name, 0, $pos);
|
||||||
|
$name = stream_socket_get_name ($client, true);
|
||||||
|
$pos = strrpos ($name, ":");
|
||||||
|
$port = substr ($name, $pos+1);
|
||||||
|
$address = substr ($name, 0, $pos);
|
||||||
|
$handlerAddress = $localAddress;
|
||||||
|
if (! key_exists ("$handlerAddress:$localPort", $this->handlers))
|
||||||
|
{
|
||||||
|
// If the address of the handler doesn't exists, the socket is maybe
|
||||||
|
// waiting on all the interfaces addresses
|
||||||
|
if (filter_var ($localAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
||||||
|
$handlerAddress = "0.0.0.0";
|
||||||
|
elseif (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
||||||
|
$handlerAddress = "::";
|
||||||
|
if (! key_exists ("$handlerAddress:$localPort", $this->handlers))
|
||||||
|
{
|
||||||
|
printf ("Can't find the handler for %s:%d\n", $localAddress,
|
||||||
|
$localPort);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//echo "Enter $address:$port > $localAddress:$localPort\n";
|
||||||
|
$handler = $this->handlers["$handlerAddress:$localPort"];
|
||||||
|
// We got a connection fork to manage it
|
||||||
|
if ($nbChild > $this->maxChild)
|
||||||
|
{
|
||||||
|
printf ("Too much child process %d : Abort the connection !!\n",
|
||||||
|
$nbChild);
|
||||||
|
@stream_socket_shutdown ($client, STREAM_SHUT_RDWR);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$pid = pcntl_fork();
|
||||||
|
if ($pid == -1)
|
||||||
|
{
|
||||||
|
throw new \Exception ("TCPServer can not fork", 500);
|
||||||
|
}
|
||||||
|
else if ($pid)
|
||||||
|
{
|
||||||
|
// parent process: return to the main loop
|
||||||
|
$nbChild++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the child. Will call the handler with the actual tcpserver object
|
||||||
|
// as parameter
|
||||||
|
$this->parent = false;
|
||||||
|
$this->socket = $client;
|
||||||
|
if (is_array ($handler))
|
||||||
|
{
|
||||||
|
$object = $handler[0];
|
||||||
|
$method = $handler[1];
|
||||||
|
$object->$method ($this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$function = $handler;
|
||||||
|
$function ($this);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** In child, get the socket to direct access
|
||||||
|
* @return resource The socket with the client
|
||||||
|
*/
|
||||||
|
public function getSock ()
|
||||||
|
{
|
||||||
|
if ($this->parent === true)
|
||||||
|
throw new \Exception ("Can not return the socket in parent mode", 500);
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not send to client : not connected", 500);
|
||||||
|
return $this->socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get an array with the peer address, peer port, local address and local
|
||||||
|
* port
|
||||||
|
* @return array array ("peer address", peer port, "local address", local
|
||||||
|
* port)
|
||||||
|
*/
|
||||||
|
public function getInfo ()
|
||||||
|
{
|
||||||
|
if ($this->parent === true)
|
||||||
|
throw new \Exception ("Can not get info in parent mode", 500);
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not send to client : not connected", 500);
|
||||||
|
$name = stream_socket_get_name ($this->socket, false);
|
||||||
|
$pos = strrpos ($name, ":");
|
||||||
|
$localPort = substr ($name, $pos+1);
|
||||||
|
$localAddress = substr ($name, 0, $pos);
|
||||||
|
$name = stream_socket_get_name ($this->socket, true);
|
||||||
|
$pos = strrpos ($name, ":");
|
||||||
|
$port = substr ($name, $pos+1);
|
||||||
|
$address = substr ($name, 0, $pos);
|
||||||
|
return array ($address, $port, $localAddress, $localPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Activate the SSL connection
|
||||||
|
* Put the socket in blocking mode, as it is mandatory to have SSL connection
|
||||||
|
* @param boolean $val True to activate, false to disable SSL
|
||||||
|
* @param integer $cryptoMethod The cryptoMethod allowed
|
||||||
|
*/
|
||||||
|
public function cryptoEnable ($val,
|
||||||
|
$cryptoMethod = STREAM_CRYPTO_METHOD_TLS_SERVER)
|
||||||
|
{
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not send to server $this->ipOrName : ".
|
||||||
|
"The server is not connected", 500);
|
||||||
|
stream_set_blocking ($this->socket, true);
|
||||||
|
return stream_socket_enable_crypto ($this->socket, !!$val, $cryptoMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set context SSL option.
|
||||||
|
* @param array $options The ssl array to set
|
||||||
|
*/
|
||||||
|
public function setSSLOptions ($options)
|
||||||
|
{
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not send to server $this->ipOrName : ".
|
||||||
|
"The server is not connected", 500);
|
||||||
|
return stream_context_set_option ($this->socket, array ("ssl" => $options));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send data to the client
|
||||||
|
* @param mixed $data The data to send
|
||||||
|
* @return the length of data sent
|
||||||
|
*/
|
||||||
|
public function send ($data)
|
||||||
|
{
|
||||||
|
if ($this->parent === true)
|
||||||
|
throw new \Exception ("Can not send data in parent mode", 500);
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not send to client : not connected", 500);
|
||||||
|
$length = strlen ($data);
|
||||||
|
$sentLen = @fwrite ($this->socket, $data);
|
||||||
|
if ($sentLen < $length)
|
||||||
|
throw new \Exception ("Can not send data to client", 500);
|
||||||
|
return $sentLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read the data from the client.
|
||||||
|
* The connection must be established
|
||||||
|
* Use the readMode in text or binary (text by default)
|
||||||
|
* In text mode, the read return when found the first \r or the first \n.
|
||||||
|
* @param integer $maxLength Limit the length of the data from the server
|
||||||
|
* @return The content
|
||||||
|
*/
|
||||||
|
public function read ($maxLength = 1024)
|
||||||
|
{
|
||||||
|
if ($this->parent === true)
|
||||||
|
throw new \Exception ("Can not read data in parent mode", 500);
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not read from client : not connected", 500);
|
||||||
|
if ($this->readMode === "text")
|
||||||
|
{
|
||||||
|
$read = stream_get_line ($this->socket, $maxLength, "\r\n");
|
||||||
|
if ($read === false)
|
||||||
|
throw new \Exception ("Can not read from client : ".
|
||||||
|
error_get_last ()["message"], 500);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$read = @fread ($this->socket, $maxLength);
|
||||||
|
if ($read === false)
|
||||||
|
throw new \Exception ("Can not read from client : ".
|
||||||
|
error_get_last ()["message"], 500);
|
||||||
|
}
|
||||||
|
return $read;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disconnect the socket
|
||||||
|
*/
|
||||||
|
public function disconnect ()
|
||||||
|
{
|
||||||
|
if ($this->parent === true)
|
||||||
|
throw new \Exception ("Can not send data in parent mode", 500);
|
||||||
|
if ($this->socket === null)
|
||||||
|
throw new \Exception ("Can not disconnect client : not connected", 500);
|
||||||
|
@stream_socket_shutdown ($this->socket, STREAM_SHUT_RDWR);
|
||||||
|
$this->socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user