php fsockopen solution php implements multithreading

  • 2020-12-18 01:47:17
  • OfStack

Answer:
When someone wants to implement concurrency, they usually think of fork or spawn
threads, but when they found out that php doesn't support multithreading, they probably switched to a less than good language like perl.
The truth is that most of the time, you don't have to use fork or threads, and you'll get better performance than using fork or thread.
Suppose you want to set up a service to check the n servers that are running to make sure they are still running. You might write code like this:


<?php 
$hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com"); 
$timeout = 15; $status = array(); 
foreach ($hosts as $host) { 
$errno = 0; 
$errstr = ""; 
$s = fsockopen($host, 80, $errno, $errstr, $timeout); 
if ($s) { 
$status[$host] = "Connectedn"; 
fwrite($s, "HEAD / HTTP/1.0rnHost: $hostrnrn"); 
do { 
$data = fread($s, 8192); 
if (strlen($data) == 0) { break; }
$status[$host] .= $data; 
} while (true); fclose($s); 
} else { 
$status[$host] = "Connection failed: $errno $errstrn"; 
} 
} 
print_r($status); 
?>

It works fine, but it will take a long time to extend this code to manage a large number of servers before fsockopen() runs through hostname and establishes a successful connection (or a delay of $timeout seconds).
So we have to give up this code; We can establish an asynchronous connection - there is no need to wait for fsockopen to return the connection status. PHP still needs to parse hostname (so it makes sense to use ip directly), but it will return immediately after opening one connection, so we can connect to the next server.
There are two ways to do this; In PHP5, the new stream_socket_client() function can be used to directly replace fsocketopen(). Before PHP5, you had to do it yourself, using sockets extensions to solve problems.
Here's the solution in PHP5:


<?php 
$hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com"); 
$timeout = 15; 
$status = array(); 
$sockets = array(); 
foreach ($hosts as $id => $host) { 
$s = stream_socket_client("$host:80", $errno, $errstr, $timeout,STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT); 
if ($s) { 
$sockets[$id] = $s; 
$status[$id] = "in progress"; 
} else { 
$status[$id] = "failed, $errno $errstr"; 
} 
} 
while (count($sockets)) { 
$read = $write = $sockets; 
$n = stream_select($read, $write, $e = null, $timeout); 
if ($n > 0) { 
foreach ($read as $r) { 
$id = array_search($r, $sockets); 
$data = fread($r, 8192); 
if (strlen($data) == 0) { 
if ($status[$id] == "in progress") { 
$status[$id] = "failed to connect"; 
} 
fclose($r); 
unset($sockets[$id]); 
} else { 
$status[$id] .= $data; 
} 
} 
foreach ($write as $w) { 
$id = array_search($w, $sockets); 
fwrite($w, "HEAD / HTTP/1.0rnHost: " . $hosts[$id] . "rnrn"); 
$status[$id] = "waiting for response"; 
} 
} else { 
foreach ($sockets as $id => $s) { 
$status[$id] = "timed out " . $status[$id]; 
} 
break; 
} 
} 
foreach ($hosts as $id => $host) { 
echo "Host: $hostn"; echo "Status: " . $status[$id] . "nn"; 
} 
?>

We use stream_select() to wait for the connection event that sockets opens. stream_select() calls the system's select(2) function to work: the first three arguments are an array of streams that you want to use; You can read, write, and get exceptions (for each of the three parameters). stream_select() can wait for the event to occur by setting the $timeout (seconds) parameter - when the event occurs, the corresponding sockets data will be written to the parameter you pass in.
The following is the implementation of PHP4.1.0 and later. If you have included sockets(ext/sockets) support when compiling PHP, you can use the similar code above, but you need to use the ext/sockets function above to implement the functions of streams/filesystem. The main difference is that we use the following function instead
stream_socket_client() to establish a connection:


<?php 
// This value is correct for Linux, other systems have other values 
define('EINPROGRESS', 115); 
function non_blocking_connect($host, $port, &$errno, &$errstr, $timeout) { 
$ip = gethostbyname($host); 
$s = socket_create(AF_INET, SOCK_STREAM, 0); 
if (socket_set_nonblock($s)) { 
$r = @socket_connect($s, $ip, $port); 
if ($r || socket_last_error() == EINPROGRESS) { 
$errno = EINPROGRESS; return $s; 
} 
} 
$errno = socket_last_error($s); 
$errstr = socket_strerror($errno); 
socket_close($s); 
return false; 
} 
?>

Now replace stream_select() with socket_select() and socket_read() with fread()
socket_write() replace fwrite() and socket_close() replace fclose() to execute the script!
The advantage of PHP5 is that you can handle almost all stream with stream_select() - for example, you can pass include
STDIN uses it to receive keyboard input and store it in arrays, and you can also receive data from pipes opened through proc_open().
If you want PHP4.3.x to have the ability to handle streams itself, I've got an patch for you that allows fsockopen to work asynchronously. This patch will not appear in the official release of PHP. I have included the implementation of the stream_socket_client() function with this patch, so you can make your scripts compatible
PHP5.


Related articles: