I'm creating a multithreaded server, and i've stucked on this problem. To accept and handle connections i'm using socket_accept, and then creating a Connection object, adding it to array, and then fetching through all array. But for some reason, when i'm doing $connection::read (which does socket_read) the socket in this objects becomes 0. But when object is constructed, everything is fine, dumping socket returns resource, but dumping the same socket in read() throws warnings, because socket is 0. Here some code
ThreadedServer, run()
while ($this->enabled){
foreach ($this->connections as $connection){
/** #var $connection Connection */
$msg = $connection->read(); //here it's already 0, and i'm getting many warnings
if($msg){
//todo
}
}
if (($clientSocket = socket_accept($this->socket))) {
socket_getpeername($clientSocket, $addr, $port);
$this->connections[$hash = self::hash($addr, $port)] = new Connection($clientSocket, $addr, $port, $this);
$this->logger->info("New connection accepted: $hash");
}
}
Connection, in same thread with the ThreadedServer
public function __construct($socket, string $address, int $port, ThreadedServer $server)
{
$this->socket = $socket;
//socket_set_nonblock($this->socket); //not needed?
var_dump($this->socket); //here it returns 'resource'
$this->server = $server;
$this->lastUpdate = microtime(true);
$this->address = $address;
$this->port = $port;
}
public function read(){
var_dump($this->socket); //here it returns 'int (0)'
return socket_read($this->socket, 1024);
}
Related
I feel that the PHP static field is static merely throughout a request.
I have this code in my controller class:
static $a = 2;
public function debug()
{
var_dump(self::$a++);
var_dump(self::$a++);
}
No matter how many times I request debug, it outputs:
int 2
int 3
Very different from my knowledge on static in java.
Yes, static in PHP is "real" static.
What you observe is result of different application life cycle in PHP and Java.
In Java, web application run inside WebServer(HTTP server), which after initial class load, on following requests reuse what it already loaded. For this reason class(and static properties) initialization occurs only once in application life cycle.
In case of typical PHP web application it looks a bit different.
HTTP server is independent application which listens for HTTP requests and runs PHP on demand(not all HTTP requests must be passed to PHP).
PHP is run as a separate process, request is passed and after answer is received process is discarded. Every request is handled by completely separate process. Classes(and static properties) are loaded and initialized from scratch every single time.
Below is simple(very) HTTP server written in PHP which will simulate how Java WebServer works.
<?php
class Server {
private $socket;
private $routes = [];
public function __construct($address, $port, $backlog = 5) {
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
throw new Exception("socket_create() failed: reason: " . socket_strerror(socket_last_error($socket)));
}
if (socket_bind($socket, $address, $port) === false) {
throw new Exception("socket_bind() failed: reason: " . socket_strerror(socket_last_error($socket)));
}
if (socket_listen($socket, $backlog) === false) {
throw new Exception("socket_listen() failed: reason: " . socket_strerror(socket_last_error($socket)));
}
$this->socket = $socket;
}
public function listen() {
while( ($requestSocket = socket_accept($this->socket)) !== false ) {
$this->handleRequestSocket($requestSocket);
}
throw new Exception("socket_accept() failed: reason: " . socket_strerror(socket_last_error($this->socket)));
}
public function registerController($url, $controller) {
$this->routes[$url] = $controller;
}
private function handleRequestSocket($socket) {
$buffer = "";
while(false !== ($part = socket_read($socket, 1024, PHP_NORMAL_READ))){
$buffer .= $part;
if(substr($buffer, -4) == "\r\n\r\n") break;
}
$buffer = trim($buffer);
echo "\n======\n$buffer\n======\n";
$response = $this->handleRequest($buffer);
if (null === $response){
socket_write($socket, "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
} else {
socket_write($socket, "HTTP/1.1 200 OK\r\nContent-Length: ".strlen($response)."\r\n\r\n$response");
}
socket_close($socket);
}
private function handleRequest($raw) {
$lines = explode("\r\n", $raw);
$req = explode(" ", $lines[0]);
$method = $req[0];
$url = $req[1];
if(isset($this->routes[$url])) {
return (string) (is_callable($this->routes[$url]) ? $this->routes[$url]($raw) : $this->routes[$url]);
}
return null;
}
}
class ControllerWithStatic {
private static $static = 0;
public function handle() {
return "Hello from static: " . (self::$static++) . "\n";
}
}
$server = new Server($argv[1], $argv[2]);
$c = new ControllerWithStatic();
$server->registerController("/", "Hello world\n");
$server->registerController("/closure", function(){return "Hello world from closure\n";});
$server->registerController("/static", [$c, 'handle']);
$server->registerController("/static2", function(){
return (new ControllerWithStatic())->handle();
});
$server->listen();
Run it using
php server.php HOST PORT
e.g.
php server.php 127.0.0.1 8080
Now open in your browser http://127.0.0.1:8080/static or http://127.0.0.1:8080/static2 and you will get
Hello from static: 0
Hello from static: 1
Hello from static: 2
...
Number will be increasing as long as you don't restart server.
I am using ReactPHP for TCP listener component. This component listens for incoming connections and exchanges data with them. $connections array is updated as clients connect/disconnect from listener.
$loop = React\EventLoop\Factory::create();
$connections = [];
$socket = new React\Socket\Server($loop);
$socket->on('connection', function ($conn) use($loop, $db){
global $connections;
$connections[] = $conn;
$conn->on('data', function ($data) use ($conn,$loop, $db) {
global $connections;
// ...
// ...
$conn->on('close', function ($conn) use($loop, $db){
global $connections;
if(($key = array_search($conn, $connections, true)) !== FALSE) {
unset($connections[$key]);
}
});
});
$socket->listen(16555, '127.0.0.1');
$loop->run();
If client is connected via telnet 'close' will be emitted so I can remove closed connection from $connection array.
However, I have problem with some devices that connect to my listener too. If I turn off device 'close' will not be emitted.
I tried to solve problem with periodical timer:
$loop->addPeriodicTimer(10, function () use($db, $loop){
global $connections;
foreach($connections as $c) {
$remoteAddress = $c->getRemoteAddress();
$metaData = #stream_get_meta_data($c->stream);
if(!$metaData) {
if(($key = array_search($c, $connections, true)) !== FALSE) {
unset($connections[$key]);
}
}
}
});
But seems that it is not reliable enough. Function stream_get_meta_data returns valid metadata array even though client is disconnected.
It is some while since this question was asked, but I've found what works for me is to use an SplObjectStorage() to be the connection pool. This is a collection which doesn't (externally) have an index. It works quite well for connections.
https://www.php.net/manual/en/class.splobjectstorage.php
I think the source of your original problem is that you are unsetting an element within a foreach, which does not automatically update the keys, and you can end up with your objects out of sequence.
In order to iterate over a collection (or an array) with code where you may be removing one or more elements while within the loop, it can be safer to use clone.
So where your pool is:
$connections_pool = new SplObjectStorage();
To iterate you would do (per your original request)
$loop->addPeriodicTimer(10, function () use($db, $loop){
global $connections_pool;
foreach(clone($connections_pool) as $c) {
$remoteAddress = $c->getRemoteAddress();
$metaData = #stream_get_meta_data($c->stream);
if(!$metaData) {
$connections_pool->offsetUnset($c);
}
}
});
I know socket and thread is horrible in php... I'm just trying to connect my php file with socket to my server.
The problem is I got nothing from socket_read (no datas no error just returning an empty string) and I think pthread kill automatically the socket.
For now I don't need thread but I need to keep it to use it later.
My code:
<?php
class test extends Threaded
{
/*
*/
public function __construct($ip, $port, $debug = false)
{
$this->debug = $debug;
$this->ip = $ip;
$this->port = $port;
}
/*
*/
public function __destruct()
{
socket_close($this->connect);
}
/*
*/
public function start()
{
if (!($this->connect = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))) {
throw new Exception('SOCKET_CREATE');
}
if (!socket_set_option($this->connect, SOL_SOCKET, SO_RCVTIMEO, array('sec' => 5, 'usec' => 0))) {
throw new Exception('SOCKET_SET_OPTION');
}
if (!socket_connect($this->connect, $this->ip, $this->port)) {
throw new Exception('SOCKET_CONNECT');
}
$getMsg = socket_read($this->connect, 255);
echo $getMsg;
}
}
Do you know how it can works?
EDIT: I tried again today and my script works, I don't understand sometime it works sometime not ....
EDIT2: I copied my server file script to an other server with much much more traffic and it works ! So why it works on the server 2 and not on server 1 ?
Basically, I have a list of proxies. I'm wanting to separate them into SOCKS4 and SOCKS5. I'd like to code up a small PHP script to do this for me. How would I go about detecting which type it is in PHP?
You need to write yourself some little code that tries to connect with any of your proxies and inspect the socks version. Connection protocol for the different versions and error codes are documented on the wikipedia page about SOCKS.
Taking that into account, the rest is more or less standard socket connection with PHP.
Example:
$proxies = array( '66.135.131.74:1681', '172.52.61.244:48943',
'75.101.237.217:1080', '76.68.128.165:39879',);
foreach ($proxies as $index => $proxy)
{
$type = SOCKSVersion::getType($proxy);
$typeName = SOCKSVersion::getTypeName($type);
printf("Proxy #%d: %s\n", $index, $typeName);
}
Output:
Proxy #0: SOCKS4
Proxy #1: SOCKS4
Proxy #2: Unknown
Proxy #3: SOCKS4
This exemplary implementation does only check for SOCKS4 so, but it could be easily extended to test as well for SOCK4a and SOCKS5 by adding methods similar to isSocks4():
/**
* SOCKS server identifiation class.
*/
class SOCKSVersion
{
const TYPE_UNKNOWN = 0;
const TYPE_SOCKS4 = 1;
const TYPE_SOCKS4a = 2;
const TYPE_SOCKS5 = 3;
/**
* #var string[]
*/
private static $typeNames = array(
self::TYPE_UNKNOWN => 'Unknown',
self::TYPE_SOCKS4 => 'SOCKS4',
self::TYPE_SOCKS4a => 'SOCKS4a',
self::TYPE_SOCKS5 => 'SOCKS5',
);
/**
* #var int
*/
private $timeout = 30;
/**
* #var int
*/
private $host, $port;
/**
* #var string[]
*/
private $errors;
/**
* #var string[]
*/
private $socks4Errors = array(
91 => "Request rejected or failed",
92 => "Request failed because client is not running identd (or not reachable from the server)",
93 => "Request failed because client's identd could not confirm the user ID string in the request",
);
public function __construct($endpoint)
{
$this->setEndpoint($endpoint);
}
/**
* #static
* #param string $proxy
* #return int any of the TYPE_* constants
*/
public static function getType($proxy)
{
$socks = new self($proxy);
return $socks->getSocksVersion();
}
/**
* #static
* #param int $type
* #return string
*/
public static function getTypeName($type)
{
$typeNames = self::$typeNames;
if (isset($typeNames[$type])) {
return $typeNames[$type];
}
return $typeNames[self::TYPE_UNKNOWN];
}
public function setEndpoint($endpoint)
{
if (!$parts = parse_url('http://' . $endpoint)) {
throw new InvalidArgumentException(sprintf('Unable to parse endpoint "%s".', $endpoint));
}
if (empty($parts['host'])) {
throw new InvalidArgumentException('No host given.');
}
if (empty($parts['port'])) {
throw new InvalidArgumentException('No port given.');
}
$this->host = $parts['host'];
$this->port = $parts['port'];
}
/**
* #return int any of the TYPE_* constants
*/
public function getSocksVersion()
{
try {
if ($this->isSocks4()) {
return self::TYPE_SOCKS4;
}
} catch (BadFunctionCallException $e) {
$this->errors[] = sprintf("SOCKS4 Test: ", $this->host, $e->getMessage());
}
return self::TYPE_UNKNOWN;
}
public function isSocks4()
{
$socket = stream_socket_client("tcp://" . $this->host . ":" . $this->port, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT);
if (!$socket) {
throw new BadFunctionCallException(sprintf('Socket-Error #%d: %s', $errno, $errstr));
}
// SOCKS4; #link <http://en.wikipedia.org/wiki/SOCKS#Protocol>
$userId = "";
$packet = "\x04\x01" . pack("n", $this->port) . pack("H*", dechex(ip2long($this->host))) . $userId . "\0";
fwrite($socket, $packet, strlen($packet));
$response = fread($socket, 9);
if (strlen($response) == 8 && (ord($response[0]) == 0 || ord($response[0]) == 4)) {
$status = ord($response[1]);
if ($status != 90) {
throw new BadFunctionCallException(sprintf("Error from SOCKS4 server: %s.", $this->socks4Errors[$status]));
}
} else {
throw new BadFunctionCallException("The SOCKS server returned an invalid response");
}
fclose($socket);
return TRUE;
}
}
Hope this is helpful. If you introduce multiple versions, you should improve the error handling and don't connect more than once to the same host if the connection failed in a previous test.
I think the best you can do is to first try to establish a CURL connection by trying the highest version - 5.
curl_setopt($curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
This will give you the answer either way. Check curl_error after you execute it. If there is no error, you are using SOCKS5, else you are using SOCKS4.
According to RFC1928, for establishing a SOCKS connection you start by sending these bytes to the server:
1 byte SOCKS version
1 byte Number of authentication methods (n)
n bytes List of method identifiers
And server responds with
1 byte SOCKS version
1 byte Accepted method
This is common between both 4th and 5th versions of SOCKS. So you can start by one version (5, for example) and fall back to another version if server doesn't respond accordingly.
I recently found out that PHP not only has the fsock* functions, but also functions to create a server itself. I decided to experiment a little bit, and came up with this. Now, the problem is that it hangs on accept_connection() (due to the fact that it's waiting for a connection.) I found out that the solution is to use stream_set_blocking(), which as you can see, I attempted, but to no avail.
I am getting an error message, which reads:
Warning: socket_set_blocking(): supplied resource is not a valid stream resource in /home/insomniaque/workspace/PHP Socket RAT/rat.class.php on line 68
I know that accept_connection() was the problem earlier because when I would connect with a second connection, it would output the data.
<?php
/*
* Project: iRAT
*
* Created on Jan 11, 2010
* Written by Insomniaque
*
*/
class rat
{
/**
* Holds the PHP socket handle for use within the class.
*/
private $socket;
/**
* Holds an array of all the spawned sockets (child sockets) that were
* created when a user connected to the server.
*/
private $spawns = array ();
/**
* Holds the maximum number of connections.
*/
private $maxconn;
/**
* Sets all of the variables required for the class and starts the socket.
* Then it'll start looping, connecting clients and running commands.
*
* #access public
* #param $port The port to bind.
* #param $maxconn The maximum number of client connections.
*/
public function __construct($port = 0, $maxconn = 1)
{
/**
* Check to see if the user has entered 0 as the port, and create a
* random port, if so.
*/
if($port == 0)
$this->port = rand(81, 8079);
else
$this->port = $port;
/**
* Save the maximum connection number.
*/
$this->maxconn = $maxconn;
/**
* Run our function to create the socket now.
*/
if(!$this->createSocket())
{
echo "Failed creating or binding socket.\n";
return false;
}
else
{
echo "Socket has been created and binded.\n";
}
/**
* Turn non-blocking on so we can run multiple clients.
*/
socket_set_blocking($this->socket, 0);
echo "Starting the data receiving loop.\n";
$this->startLoop();
return true;
}
/**
* This function will create the socket for later use.
*
* #access private
* #return bool Returns true if the socket was created successfully,
* returns false if there was an error.
*/
private function createSocket()
{
/**
* Create a socket of IPv4 type using the TCP gateway.
*/
$this->socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
if(!$this->socket)
return false;
echo "Socket has been created.\n";
/**
* Attempt to bind the socket to localhost:[port]
*/
do
{
if(!isset($i))
$i++;
$port = $this->port;
$bind = socket_bind($this->socket, 0, $port);
if(!$bind)
{
$i++;
$this->port = rand(79, 8079);
}
} while(!$bind && $i <= 5);
if($i == 5)
return false;
echo "Port ".$this->port." has been binded to the RAT.\n";
return true;
}
/**
* Start a loop running on our socket. We will check if we are getting
* data, accept connections and run any commands. When the connection
* is closed, we will return true.
*
* #access private
* #return bool Returns false if the socket can't be listened to. Otherwise
* returns true when the socket is closed.
*/
private function startLoop()
{
if(socket_listen($this->socket, 3))
{
while(true)
{
if(($newspawn = socket_accept($this->socket)) !== false)
{
$this->spawns[] = $newspawn;
echo "A new spawn has connected.";
} else
echo "No new socket";
sleep(1000);
foreach($this->spawns as $key => $spawn)
{
$data = trim(socket_read($spawn, 1024));
if(strlen($data) > 0)
{
if($data == "exit")
{
socket_close($spawn);
unset($this->spawns[$key]);
echo "Spawn killed.\n";
}
if($data == "kill")
{
foreach($this->spawns as $key => $spawn)
{
socket_close($spawn);
unset($this->spawns[$key]);
}
socket_close($this->socket);
echo "Socket closed.\n";
return true;
}
else
{
echo "Data: " . $data . "\n";
}
}
}
}
}
else
{
echo "Failure receiving data.\n";
return false;
}
}
}
?>
Thanks in advance,
John
Use socket_set_nonblock() instead of socket_set_blocking() for a socket resource create by socket_create().
socket_set_blocking() is an alias for
stream_set_blocking() which only works for (php-)streams, like the result of fopen() or stream_socket_create()
edit: You can also use socket_select() or stream_select() to handle both new connections and client data packet, e.g.
private function createSocket()
{
$this->socket = stream_socket_server('tcp://0.0.0.0:'.(int)$this->port, $errno, $errstr);
if(!$this->socket) {
$this->socket = null;
echo "stream_socket_server failed : $errstr ($errno)\n";
return false;
}
echo "Port ".$this->port." has been bound to the RAT.\n";
return true;
}
public function startLoop() {
if ( is_null($this->socket) ) {
return false;
}
$write = array(); $exception=array();
while( !$this->shutdown ) {
// stream_select() alters the array, so $read has to be re-constructed in each iteration somehow
$read = array_merge(array($this->socket), $this->spawns);
// you might want to check $exception as well
if ( 0!==stream_select($read, $write, $exception, 4) ) {
// $now $read only contains those sockets, that will not block
// for fread/accept operations
foreach($read as $s) {
if ( $s===$this->socket ) {
$this->onAccept();
}
else {
$this->onClientPacket($s);
}
}
}
}
$this->shutdown();
}
Keep in mind that if a client sends two commands like e.g.
$fp1 = stream_socket_client("tcp://localhost:81", $errno, $errstr, 30);
fwrite($fp1, "1 blabla");
fwrite($fp1, "exit");
it doesn't necessarily mean your server will get two separate messages.
I believe you would be interested in using stream_socket_create() as opposed to socket_create(), which should return a valid resource to be used with socket_set_blocking().
Note that you will also need to utilize stream_socket_accept() to begin accepting connections on your newly created socket.