I am writing HTTP Server using PHP sockets and currently it works normally but the problem appears then I am running apache benchmark with 25 level concurrency and 1000 request it just freezes and timeouts, but with 24 concurrency level it works perfectly fine. Maybe someone will figure out a flaw in my architecture because I don't think that concurrency here is a problem because kernel should balance everything.
Here is my minimal reproducible code:
<?php
$server = socket_create(AF_INET, SOCK_STREAM, 0);
socket_bind($server, '127.0.0.1', '9090');
socket_set_nonblock($server);
socket_listen($server);
$selectSockets = [$server];
$null = null;
for ($i = 0; $i < 8; $i++) {
$pid = pcntl_fork();
if ($pid > 0) {
} else {
while (true) {
$result = #socket_accept($server);
if (false === $result) {
$errCode = socket_last_error($server);
if (0 !== $errCode) {
throw new Exception(socket_strerror($errCode));
}
} else {
$readSockets = [$result];
$buffer = '';
$requestFinished = false;
while (!$requestFinished) {
$count = #socket_select($readSockets, $null, $readSockets, 2);
if ($count) {
$read = socket_read($result, 4096);
if ($read === '0' || $read) {
$requestFinished = true;
} else {
$buffer .= $read;
}
}
}
socket_write($result, 'HTTP/1.1 200 OK' . "\r\n\r\n" . 'Hello World');
socket_close($result);
}
#socket_select($selectSockets, $null, $null, 10);
}
}
}
pcntl_wait($status);
Command I use to test:
ab -c 25 -n 1000 http://127.0.0.1:9090/
Related
I am programming a PHP application that uses a serial device to do stuff with GSM modem. So you can communicate with the serial port via screen and you would get a response when you write a command.
I use PHP to communicate with my serial port and use sleep to wait between the commands, I se fopen with w+ flags and fwrite to send the commands. I tried using the fread function to check if the response is there, but it wasn't. How could that be done in PHP?
conceptually f_read should work if the port is configured correctly. there is a library php-serial on https://github.com/Xowap/PHP-Serial
this is the readPort() function :
public function readPort($count = 0)
{
if ($this->_dState !== SERIAL_DEVICE_OPENED) {
trigger_error("Device must be opened to read it", E_USER_WARNING);
return false;
}
if ($this->_os === "linux" || $this->_os === "osx") {
// Behavior in OSX isn't to wait for new data to recover, but just
// grabs what's there!
// Doesn't always work perfectly for me in OSX
$content = ""; $i = 0;
if ($count !== 0) {
do {
if ($i > $count) {
$content .= fread($this->_dHandle, ($count - $i));
} else {
$content .= fread($this->_dHandle, 128);
}
} while (($i += 128) === strlen($content));
} else {
do {
$content .= fread($this->_dHandle, 128);
} while (($i += 128) === strlen($content));
}
return $content;
} elseif ($this->_os === "windows") {
// Windows port reading procedures still buggy
$content = ""; $i = 0;
if ($count !== 0) {
do {
if ($i > $count) {
$content .= fread($this->_dHandle, ($count - $i));
} else {
$content .= fread($this->_dHandle, 128);
}
} while (($i += 128) === strlen($content));
} else {
do {
$content .= fread($this->_dHandle, 128);
} while (($i
+= 128) === strlen($content));
}
return $content;
}
return false;
}
( How to Read RS232 Serial Port in PHP like this QBasic Program )
I have socket server script that continuously running listening for the GPS device that runs in PHP CLI,my problem is that my socket will freeze if it executes long time,how do I prevent this so that my script will not freeze.I have no idea on this socket.this is the first time that I use socket connection.I created variable to check if it lapses to 5 mins,then I break the loop and start it over.I don't know if this is the correct to handle this or to prevent freezing.
I appreciate someone can help my problem.
I updated my code
<?php
error_reporting(-1);
ini_set('display_errors', 1);
set_time_limit (0);
for(;;){
$FIVE_MINUTES = 300000000;
$TIME_TO_EXIT = 0;
$address_server = 'xxx.xxx.xx.xx';
$port_server = xxxx;
$isTrue = true;
socketfunction($address_server,$port_server,$isTrue,$TIME_TO_EXIT, $FIVE_MINUTES);
}
function socketfunction($address,$port,$done){
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($sock, $address, $port);
socket_listen($sock);
$clients = array($sock);
while ($done ) {
$file = fopen('txt.log','a');
$read = $clients;
$write = NULL;
$except = NULL;
$tv_sec = 0;
$TIME_TO_EXIT++;
if(TIME_TO_EXIT>$FIVE_MINUTES){
$done =false;
break 2;//exit while loop
}
if (socket_select($read, $write , $except, $tv_sec) < 1){
continue;
}
// checking client
if (in_array($sock, $read)) {
$clients[] = $newsock = socket_accept($sock);
$key = array_search($sock, $read);
unset($read[$key]);
}
//handle client for reading
foreach ($read as $read_sock) {
$data = #socket_read($read_sock, 1024, PHP_NORMAL_READ);
if ($data === false) {
$key = array_search($read_sock, $clients);
unset($clients[$key]);
echo "client disconnected.\n";
echo "Remaining ".(count($clients) - 1)."client(s) connected\r\n";
continue;
}
$data = trim($data);
if (!empty($data)) {
echo("Returning stripped input\n");
fwrite($file,$data."\n");
}
} // end of reading foreach
fclose($file);
}//end while
socket_close($sock);
}
?>
Thank you in advance.
From what I see in the code that you have included in your question, you never change the value of $done so your while-loop will run forever.
So what to do is to change the value of $done to false when you have achieved what you wanted with your call. So for example after fwrite() you write:
$done = false;
Is there any way (other than checking for ping responses) to detect when a client stream (I don't know if a stream would be any different from sockets) becomes unavailable (ie there is no longer any connection but no clean disconnection was made)?
Using this code:
#!/usr/bin/env php
<?php
$socket = stream_socket_server(
'tcp://192.168.1.1:47700',
$errno,
$errstr,
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,
stream_context_create(
array(),
array()
)
);
if (!$socket) {
echo 'Could not listen on socket'."\n";
}
else {
$clients = array((int)$socket => $socket);
$last = time();
while(true) {
$read = $clients;
$write = null;
$ex = null;
stream_select(
$read,
$write,
$ex,
5
);
foreach ($read as $sock) {
if ($sock === $socket) {
echo 'Incoming on master...'."\n";
$client = stream_socket_accept(
$socket,
5
);
if ($client) {
stream_set_timeout($client, 1);
$clients[(int)$client] = $client;
}
}
else {
echo 'Incoming on client '.((int)$sock)."...\n";
$length = 1400;
$remaining = $length;
$buffer = '';
$metadata['unread_bytes'] = 0;
do {
if (feof($sock)) {
break;
}
$result = fread($sock, $length);
if ($result === false) {
break;
}
$buffer .= $result;
if (feof($sock)) {
break;
}
$continue = false;
if (strlen($result) == $length) {
$continue = true;
}
$metadata = stream_get_meta_data($sock);
if ($metadata && isset($metadata['unread_bytes']) && $metadata['unread_bytes']) {
$continue = true;
$length = $metadata['unread_bytes'];
}
} while ($continue);
if (strlen($buffer) === 0 || $buffer === false) {
echo 'Client disconnected...'."\n";
stream_socket_shutdown($sock, STREAM_SHUT_RDWR);
unset($clients[(int)$sock]);
}
else {
echo 'Received: '.$buffer."\n";
}
echo 'There are '.(count($clients) - 1).' clients'."\n";
}
}
if ($last < (time() - 5)) {
foreach ($clients as $id => $client) {
if ($client !== $socket) {
$text = 'Yippee!';
$ret = fwrite($client, $text);
if ($ret !== strlen($text)) {
echo 'There seemed to be an error sending to the client'."\n";
}
}
}
}
}
}
if ($socket) {
stream_socket_shutdown($socket, STREAM_SHUT_RDWR);
}
and a sockets client on a different computer, I can connect to the server, send and receive data, and disconnect cleanly and everything functions as expected. If, however, I pull the network connection on the client computer, nothing is detected on the server side - the server keeps on listening to the client socket, and also writes to it without any error manifesting itself.
As I understand it, calling feof($stream) will tell you if the remote socket disconnected, but I'm not absolutely certain about that. I'm using ping/pong myself while continuing to research a solution.
You need to set a socket timeout, in which case you get an error if a client does not respond in a timely fashion.
Check PHP's stream_set_timeout function:
http://www.php.net/manual/en/function.stream-set-timeout.php
Also check socket_set_option:
http://php.net/manual/en/function.socket-set-option.php
and finally, check out this great article on how to use sockets in PHP effectively:
"PHP Socket Programming, done the Right Way™"
http://christophh.net/2012/07/24/php-socket-programming/
I am using the following websocket php class as my server: PHPWebSocket. It works well in every way I could have hoped for, but I've run into a bit of a problem.
I want to update the positions of connected clients (they can walk around) at a rate of maybe every 0.3 seconds. I figured it would be a simple matter of looking for the while loop and adding a heartbeat event there, but here it becomes tricky.
// server state functions
function wsStartServer($host, $port) {
if (isset($this->wsRead[0])) return false;
if (!$this->wsRead[0] = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) {
return false;
}
if (!socket_set_option($this->wsRead[0], SOL_SOCKET, SO_REUSEADDR, 1)) {
socket_close($this->wsRead[0]);
return false;
}
if (!socket_bind($this->wsRead[0], $host, $port)) {
socket_close($this->wsRead[0]);
return false;
}
if (!socket_listen($this->wsRead[0], 10)) {
socket_close($this->wsRead[0]);
return false;
}
$this->log("Server starting");
$write = array();
$except = array();
$nextPingCheck = time() + 1;
while (isset($this->wsRead[0])) {
$changed = $this->wsRead;
$result = socket_select($changed, $write, $except, 1);
**beat()**
if ($result === false) {
socket_close($this->wsRead[0]);
return false;
}
elseif ($result > 0) {
foreach ($changed as $clientID => $socket) {
if ($clientID != 0) {
// client socket changed
$buffer = '';
$bytes = #socket_recv($socket, $buffer, 4096, 0);
if ($bytes === false) {
// error on recv, remove client socket (will check to send close frame)
$this->wsSendClientClose($clientID, self::WS_STATUS_PROTOCOL_ERROR);
}
elseif ($bytes > 0) {
// process handshake or frame(s)
if (!$this->wsProcessClient($clientID, $buffer, $bytes)) {
$this->wsSendClientClose($clientID, self::WS_STATUS_PROTOCOL_ERROR);
}
}
else {
// 0 bytes received from client, meaning the client closed the TCP connection
$this->wsRemoveClient($clientID);
}
}
else {
// listen socket changed
$client = socket_accept($this->wsRead[0]);
if ($client !== false) {
// fetch client IP as integer
$clientIP = '';
$result = socket_getpeername($client, $clientIP);
$clientIP = ip2long($clientIP);
if ($result !== false && $this->wsClientCount < self::WS_MAX_CLIENTS && (!isset($this->wsClientIPCount[$clientIP]) || $this->wsClientIPCount[$clientIP] < self::WS_MAX_CLIENTS_PER_IP)) {
$this->wsAddClient($client, $clientIP);
}
else {
socket_close($client);
}
}
}
}
}
if (time() >= $nextPingCheck) {
$this->wsCheckIdleClients();
$nextPingCheck = time() + 1;
}
}
return true; // returned when wsStopServer() is called
}
To me it doesn't seem like there is any specific timer that says it should only run once per second, but it does. I am not talking about the ping check, even if I place my heartbeat call directly after the while (isset($this->wsRead[0])) {, it will only trigger once per second, and it has me completely stumped.
Am I looking in the wrong place? Is there something about PHP's websocket implementation that slows down this while loop?
You have a one second timeout in your socket_select call - that's probably where your delay is introduced. If you want that call to be non-blocking, you can pass a zero for that timeout.
I am writing a WebSocket server. While handshaking is successful and server can send encoded data using RFC standard, socket_select() can only detect changes when a new client is connected, not when the client send data to the server. What is happening?
class Server{
private $address;
private $port;
private $master;
private $sockets;
private $stream_sockets;
private $clients;
private $verbose_mode;
function __construct($address = '127.0.0.1', $port = 5001, $verbose_mode = true){
$this->address = $address;
$this->port = $port;
$this->verbose_mode = $verbose_mode;
$this->console("Socket server is starting...");
//socket creation
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if(!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1))
{
$this->console("Failed to set socket option: ".socket_strerror(socket_last_error()));
}
if(!socket_set_nonblock($socket))
{
$this->console("Failed to set socket nonblock: ".socket_strerror(socket_last_error()));
}
if(!is_resource($socket))
{
$this->console("Failed to create socket: ".socket_strerror(socket_last_error()));
}
if(!socket_bind($socket, $this->address, $this->port))
{
$this->console("Failed to bind socket: ".socket_strerror(socket_last_error()));
}
if(!socket_listen($socket, 20))
{
$this->console("Failed to listen: ".socket_strerror(socket_last_error()));
}
$this->master = $socket; //add current socket resource as master socket
$this->sockets = array($socket);//add current resource to all sockets
$this->console("Socket server started on {$this->address}:{$this->port}");
}
public function run(){
$this->console("Start running...");
$write = array();
$except = array();
while(isset($this->sockets[0])){
$changed_sockets = $this->sockets;
$result = socket_select($changed_sockets, $write, $except, 1 ,0);
//#stream_select($changed_sockets, $write = null, $except = null, 0, 5000);
if($result > 0)
{
$this->console("number of sockets: ".count($this->sockets));
$this->console("number of changed sockets: ".$result);
foreach($changed_sockets as $socket)
{
if($socket == $this->master)//self changed
{
if(($accepted_socket = socket_accept($this->master))!== false)//incoming connection
{
$this->connect($accepted_socket);//add as a client to the pool with info to be tracked without handshaking at first
}
else
{
$this->console("Socket error: ".socket_strerror(socket_last_error()));
}
}
else //must be others in sockets pool
{
$this->console("Finding socket associated with the client...");
$client = $this->get_client_by_socket($socket); //get client object to track them using socket that they are associated with
if($client){
$this->console("receiving data from the client.");
$bytes = #socket_recv($socket, $data, 2048, MSG_DONTWAIT);
$this->console("byte size received: $bytes");
if(!$client->get_handshake())//check if handshaking has done
{
$this->console("handshaking...");
$this->handshake($client, $data);//handshaking if it is not done previously
}
else if($bytes === 0)
{
$this->disconnect($client);
}
else
{
$this->console("incoming data from client {client->get_id()}");
$this->read($client, $data);//read from client if there are changes in sockets
}
}
}
}
}
}
}
private function slingshot($client, $read){
$send="00:00:00:00".",DM,SAY,0,".$read;
fwrite($client->get_stream_socket(), $send);//output to apollo
//workaround for apollo
if($client->get_initial())
{
$initial = 7;
$continue = 0;
}
else
{
$initial = 8;
$continue = 1;
}
while(TRUE)
{
//input from iris
$num = fgets($client->get_stream_socket(), $initial);//$number of words
if(ltrim($num) > 0)
{
$res = fgets($client->get_stream_socket(), ltrim($num)+1);
if($res!="")
{
fgets($fp,1);
$client->set_initial(false);
$res = $num.$res;
$res = substr($res,6+$continue);
//output to client
$message = rtrim($res);
send($client, $message);
break;
}
}
}
}
private function read($client, $received){
$read = $this->unmask($received);
$this->console("received from client: ".$read);
if($read == "##client exit##") {
$this->console("Killing a child process");
posix_kill($client->get_pid(), SIGTERM);
$this->console("Process {$client->get_pid()} is terminated.");
}
else
{
$this->console("start a child process");
$pid = pcntl_fork();
if($pid == -1)
{
die('could not fork.');
}
else if($pid)
{
$client->set_pid($pid);
}
else
{
//we are the child
$this->slingshot($client, $read);
}
}
}
private function disconnect($client){
$this->console("Disconnecting client #{$client->get_id()}");
$i = array_search($client, $this->clients);//search client in clients pool
$j = array_search($client->get_socket(), $this->sockets);//search client's socket in socket pool
if($j >= 0)
{
array_splice($this->sockets, $j, 1);
socket_close($client->get_socket());
$this->console("Socket closed.");
}
if($i >= 0)
{
array_splice($this->clients, $i, 1);
}
$this->console("Client #{$client->get_id()} disconnected.");
}
private function unmask($payload) {
$length = ord($payload[1]) & 127;
if($length == 126)
{
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
}
elseif($length == 127)
{
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
}
else
{
$masks = substr($payload, 2, 4);
$data = substr($payload, 6);
}
$text = '';
for ($i = 0; $i < strlen($data); ++$i){
$text .= $data[$i] ^ $masks[$i%4];
}
return $text;
}
private function encode($text){
// 0x1 text frame (FIN + opcode)
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);
if($length <= 125)
{
$header = pack('CC', $b1, $length);
}
elseif($length > 125 && $length < 65536)
{
$header = pack('CCS', $b1, 126, $length);
}
elseif($length >= 65536)
{
$header = pack('CCN', $b1, 127, $length);
}
return $header.$text;
}
private function send($client, $text){
$this->console("Client {$client->get_id()}<<".$text);
$text = $this->encode($text);
if(socket_write($client->get_socket(), $text, strlen($text)) === false) {
$this->console("Unable to write to client #{$client->get_id()}'s socket");
$this->disconnect($client);
}
}
private function start_process(){
$this->console("start a child process");
$pid = pcntl_fork();
if($pid == -1)
{
die('could not fork.');
}
else if($pid)
{
$client->set_pid($pid);
}
else
{
//we are the child
$this->send($client, "something to be sent.");
}
}
private function handshake($client, $headers){//data as headers
$this->console("Getting client WebSocket version...");
if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/", $headers, $match))
{
$version = $match[1];
}
else
{
$this->console("The client doesn't support WebSocket.");
}
$this->console("Client WebSocket version is {$version}, (required: 13)");
if($version == 13)
{
$this->console("Getting headers...");
if(preg_match("/GET (.*) HTTP/", $headers, $match))
{
$root = $match[1];
}
if(preg_match("/Host: (.*)\r\n/", $headers, $match))
{
$host = $match[1];
}
if(preg_match("/Origin: (.*)\r\n/", $headers, $match))
{
$origin = $match[1];
}
if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $headers, $match))
{
$key = $match[1];
}
$this->console("client Headers are:");
$this->console("\t- Root: ".$root);
$this->console("\t- Host: ".$host);
$this->console("\t- Origin: ".$origin);
$this->console("\t- Sec-WebSocket-Key: ".$key);
$this->console("Generating Sec-WebSocket-Accept key...");
$acceptKey = $key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$acceptKey = base64_encode(sha1($acceptKey, true));
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n".
"Upgrade: websocket\r\n".
"Connection: Upgrade\r\n".
"Sec-WebSocket-Accept: $acceptKey".
"\r\n\r\n";
$this->console("sending this response to the client #{$client->get_id()}:\r\n".$upgrade);
socket_write($client->get_socket(), $upgrade);
$client->set_handshake(true);
$this->console("Handshake is successfully done!");
return true;
}
else
{
$this->console("WebSocket version 13 is required (the client supports version {$version})");
return false;
}
}
private function get_client_by_socket($socket){
foreach($this->clients as $client)//get all client objects from the pool and check one by one
{
if($client->get_socket() == $socket)//if socket returned from the client matches with parameter
{
$this->console("client found");
return $client;
}
}
return false;//no such client
}
private function connect($socket){
$this->console("creating client...");
$client_id = uniqid();
while(true){
$stream_socket = #stream_socket_client("tcp://127.0.0.1:10000", $errno, $errstr);
if($stream_socket)
{
$this->console("Apollo client created for client #$client_id.");
break;
}
else
{
$this->console("creation failed. Attempting to recreate Apollo client.");
}
}
$client = new Client($client_id, $socket, $stream_socket);
$this->clients[] = $client; //add the socket as client to be tracked
$this->sockets[] = $socket;//add socket as resource to sockets pool
$this->stream_sockets[] = $stream_socket;//add socket as resource to stream sockets pool
$this->console("Client #{$client->get_id()} is successfully created!");
}
private function console($text, $continue = true){
if(!$continue)
{
die($text);
}
if($this->verbose_mode)
{
echo date('[Y-m-d H:i:s] ').$text."\r\n";
}
}
}
That's because you have to accept the new incoming connection using socket_accept(), passing in the socket you've used for socket_listen().
The resulting socket can be used to check for incoming data as expected.
Edit
It seems that you forgot to add $write = $this->sockets; before your call to socket_select().
I figured out that to enable the socket select to detect data from the client, socket server has to send a dummy message to the client first immediately after handshake.