PHP Socket Server Hangs - php

I've worked hours and hours on this, and I can't figure it out. I've browsed the posts on here trying to find the solution also and to no avail.
I have a Socket Server setup for my browser-based game. I've stripped it down trying to find the issue, and it seems that fread is hanging because if I comment out "fread($fp, 10024);" then it runs fine, but of course doesn't read the response.
While I debug this, I've broken the files down to the basics.
I have the Socket Server: ChatServer.php
set_time_limit(0);
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_get_option($sock, SOL_SOCKET, SO_REUSEADDR);
socket_bind($sock, "127.0.0.1", "9990");
socket_listen($sock, 4);
$chatContent = "Testing, 1, 2, 3.";
do
{
$childSocket = socket_accept($sock);
$incomingData = socket_read($childSocket, 12048);
socket_write($childSocket, $chatContent, strlen($chatContent));
} while(true);
Then I have Test.php which should open the socket and read a response.
$fp = fsockopen("127.0.0.1", "9990", $errno, $errstr, 5);
echo $errstr . "<br />";
echo fread($fp, 10024);
$errstr doesn't display an error, because when I start ChatServer.php then reload Test.php, it never reloads. It hangs for minutes and lags my entire server. I'm running on a VPS. This worked fine before, then suddenly stopped working, and I can't figure out why.
Edit: Thanks to GigaWatt, I was able to get it working. Here is the code I used if you have the same issue. :)
set_time_limit(0);
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_get_option($sock, SOL_SOCKET, SO_REUSEADDR);
socket_bind($sock, "127.0.0.1", "9990");
socket_listen($sock, 4);
$chatContent = "Testing, 1, 2, 3.";
do
{
$childSocket = socket_accept($sock);
$meta = stream_get_meta_data($sock);
if($meta['unread_bytes'] > 0) {
$incomingData = socket_read($childSocket, $meta['unread_bytes']);
}
socket_write($childSocket, $chatContent, strlen($chatContent));
} while(true);
Just use stream_get_meta_data and then unread_bytes.

The call to socket_read is a blocking call, meaning everything stops until the specified number of bytes have been read.
If you need to continue processing, consider using stream_get_meta_data (the unread_bytes value) to check how much unread data is waiting.
Once it reaches the desired threshold, then call socket_read.
http://us3.php.net/manual/en/function.stream-get-meta-data.php

It does exactly what you tell it to: waits for 12048 / 10024 bytes of data or socket being closed.
You might be interested in using a non-blocking socket (socket_set_nonblock/stream_set_blocking) and a socket_select loop or libevent.

Related

PHP socket_accept throws warnings in non-blocking mode

I want to create an easy PHP server (TCP-based) that would serve actual time and close the connection immediately. I've done that already. I wanted to add crtl-C handling so I needed to replace blocking socket_accept with non-blocking (this is because when the blocking socket_accept instruction is reached and I send SIGINT /ctrl-C/ then the server will still be alive until the first client is server and then it closes itself - and I didn't want this behavior).
My current code looks like this:
<?php
error_reporting(E_ALL);
ob_implicit_flush();
if ($argc != 2)
die("Wrong params");
$address = 'localhost';
$port = $argv[1];
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false)
die(socket_strerror(socket_last_error()) . "\n");
if (socket_bind($sock, $address, $port) === false)
die(socket_strerror(socket_last_error($sock)) . "\n");
if (socket_listen($sock, 5) === false)
die(socket_strerror(socket_last_error($sock)) . "\n");
socket_set_nonblock($sock);
$remote_host = $remote_port = $msgsock = null;
declare(ticks = 1);
function sig_handler($signo)
{
switch ($signo) {
case SIGTERM:
case SIGINT:
global $sock;
socket_shutdown($sock);
socket_close($sock);
echo "Terminating...\n";
exit;
}
}
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGINT, "sig_handler");
echo "Starting server\n";
while (1) {
do {
$msgsock = socket_accept($sock);
usleep(100000);
} while ($msgsock === false);
socket_getpeername($msgsock, $remote_host, $remote_port);
echo "Connection made from {$remote_host}:{$remote_port}\n";
$msg = date('r', time()) . "\n";
socket_write($msgsock, $msg, strlen($msg));
socket_close($msgsock);
};
socket_close($sock);
Everything works fine except for one detail... I get the following PHP warning every 0.1 second (= 100000 microseconds):
PHP Warning: socket_accept(): unable to accept incoming connection [11]: Resource temporarily unavailable in /home/tomasz/Development/Python/twisted/time-server.php on line 55
PHP Stack trace:
PHP 1. {main}() /home/tomasz/Development/Python/twisted/time-server.php:0
PHP 2. socket_accept() /home/tomasz/Development/Python/twisted/time-server.php:55
What I've tried to achieve is non-blocking accept: PHP uses the server socket, checks if there's any connection awaiting to be served. I not - wait 0.1 second. If there is a pending connection, serve it. All functionality is OK except that I've got no idea why is this warning thrown - I just want to check if there's any connection to be served. Modifying error_reporting to E_ERROR makes the warnings quiet, but I hope there's a better way to solve that...
edit:
modifying socket_accept($sock) to #socket_accept($sock) will just suspress warnings from being thrown, but still this doesn't state why it is thrown...
I located your question while searching for a solution to the exact same problem. Additionally, I found a few other posts asking about the same thing, but also without solutions. Some posts indicated that there was no way to prevent socket_accept() from throwing warnings, as it is by design to indicate "there is no waiting connection to accept." I couldn't confirm this as it doesn't seem to be mentioned in the PHP manual page for socket_accept()
Like you, I wasn't satisfied with using any sort of error suppression. It seemed the next logical step was to wrap socket_accept() in some sort of conditional so that it would only execute if I knew there was a connection waiting. I couldn't find anything that did this. But when using stream_*() functions instead of just socket_*() functions, it looks like there is. So I switched out to using streams (seems there are some other advantages as well), as follows:
if (( $socket = stream_socket_server( "tcp://$address:$port", $errno, $errstr )) === FALSE ) {
die( "failed to create socket: $errstr\n" );
}
echo "Waiting for clients to connect...\n";
while ( $server_listening ) {
$read = array( $socket );
$array = array();
if ( stream_select( $read, $array, $array, 0 )) {
$connection = stream_socket_accept( $socket, 0 );
client_handler( $socket, $connection );
} else {
usleep( 100 );
}
}
This seems to work well. My script only attempts to accept the connection if it first determines that there is a connection waiting. After going through this, I did find that there was an equivalent socket function for this, socket_select(), which appears to work the same way. So you may be able to do something similar if you wanted to stick with the socket_*() functions.
On a side note, I'm glad I made the switch to streams as they seem easier to work with. And since my application is limited to TCP, I don't seem to be missing any functionality that I would get with more low-level sockets.
There is something that does that, and it's select(). Nonblocking socket IO should use select(), period. In PHP that means socket_select(). See any reference on BSD sockets for more info.

PHP5 fsockopen connection error break site, no matter how I capture it

I'm trying to use fsockopen to communicate with a game server, which responds with some basic stats. It works perfectly when the server is online, but if the server is ever offline, the following code causes php to stop displaying the page that reads the data.
try {
$socket = fsockopen($host, $port, $errno, $errstr, 10);
if ($socket !== false) {
fwrite($socket, "\xFE");
$data = "";
$data = fread($socket, 1024);
fclose($socket);
if ($data !== false && substr($data, 0, 1) == "\xFF") {
// get into
} else {
// Server did not send back proper data, or reading from socket failed.
print "Server not available.";
}
} else {
// ...
}
} catch(Exception $e){
// ...
}
I've tried the try/catch, I've tried adding a custom handler to the exception. My only idea is to run this outside of the web requests and store the response so that the web request isn't initiating it.
Any thoughts?
First, I'd add a couple of echo commands, either side of the fsockopen call:
echo date("Y-m-d H:i:s")."Before open\n";
$socket = fsockopen($host, $port, $errno, $errstr, 10);
echo date("Y-m-d H:i:s")."After open (socket=".($socket===false?"Bad":"OK")."\n";
This is to confirm the 10 second timeout is working. If you never see the second message then the timeout is not working, and the problem is more obscure.
Anyway, if you are getting a valid $socket, but the lock-up happens later, then try:
if ($socket !== false) {
stream_set_timeout($socket,2); //2 second timeout
stream_set_blocking($socket,false); //no blocking
fwrite($socket, "\xFE");
...
P.S. If adding those two commands solves the problem, then experiment to see if just one of them solves it. That would give a big clue what the real problem is.
It seems that by moving the logic outside the html generation worked. The lookup happens before any html is rendered, so if it fails it doesn't interrupt the html output.

receiving VLC generated MP4 with PhP is a little messy

Im using VCL to broadcast to my localhost, 127.0.0.1 with UDP (legacy) method. To catch the traffic, I use this code:
$address = '127.0.0.1';
$port = 1234;
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_bind($sock, $address, $port) or die('Could not bind to address');
$f = fopen ('output', 'w');
fclose ($f);
$sock = stream_socket_server('udp://127.0.0.1:1234', $errno, $errstr, STREAM_SERVER_BIND);
while(1)
{
$a = stream_socket_recvfrom($sock, 65536);
$f = fopen('output', 'a');
fwrite ($f, $a);
fclose ($f);
#ob_flush();
}
this logs the packets and saves, I rename it to .MP4 and open - well, the result is a little messy. I can recognize the output, the top screen is visible, the lower part is not good. I tried to capture it with another VCL player, and there were no problem.
Here is your code with a lot of useless stuff removed and a few efficiency improvements. Try it out and see what happens. It may or may not fix the problem, but report back with what happens and we'll take it from there.
// Settings
$address = '127.0.0.1';
$port = 1234;
$outfile = "output.mp4";
// Open pointers
if (!$ofp = fopen($outfile, 'w'))
exit("Could not open output file for writing");
if (!$ifp = stream_socket_server("udp://$address:$port", $errno, $errstr, STREAM_SERVER_BIND))
exit("Could not create listen socket ($errno: $errstr)");
// Loop and fetch data
// This method of looping is flawed and will cause problems because you are using
// UDP. The socket will never be "closed", so the loop will never exit. But you
// were looping infinitely before, so this is no different - we can address this
// later
while (!feof($ifp)) {
if (!strlen($chunk = fread($ifp, 8192))) continue;
fwrite($ofp, $chunk);
}
// Close file pointers
fclose($ofp);
#fclose($ifp);

Creating s simple client/server UDP example in PHP

I'm trying to make a simple UDP client server example in PHP but I face an error.
This is the client :
$fp = stream_socket_client("udp://192.168.0.12:12478", $errno, $errstr);
if ($fp)
{
fwrite($fp, "TEST 1 TEST 2 TEST 3");
$buf = fgets($fp);
var_dump($buf);
fclose($fp);
}
This is the server :
$socket = stream_socket_server("udp://192.168.0.12:12478", $errno, $errstr, STREAM_SERVER_BIND);
if ($socket)
{
while ($conn = stream_socket_accept($socket)) {
fwrite($conn, date("D M j H:i:s Y\r\n"));
fclose($conn);
}
fclose($socket);
}
All executions end with :
Warning: stream_socket_accept(): accept failed: Operation not supported
Basically, this is the example given in all PHP documentations but I can't figure what is wrong in it. Any help is greatly appreciated.
Thanks.
Here is the warning on the very same page
Warning
This function should not be used with UDP server sockets. Instead,
use stream_socket_recvfrom() and
stream_socket_sendto().
according to the documentation: "you cannot make a silk piurse from a sow's ear"
stream_socket_connect is intended for STREAMS, not datagram packets. recvfrom would be more likely to work in this scenario.

Does my basic PHP Socket Server need optimization?

Like many people, I can do a lot of things with PHP. One problem I do face constantly is that other people can do it much cleaner, much more organized and much more structured. This also results in much faster execution times and much less bugs.
I just finished writing a basic PHP Socket Server (the real core), and am asking you if you can tell me what I should do different before I start expanding the core. I'm not asking about improvements such as encrypted data, authentication or multi-threading.
I'm more wondering about questions like "should I maybe do it in a more object oriented way (using PHP5)?", or "is the general structure of the way the script works good, or should some things be done different?". Basically, "is this how the core of a socket server should work?"
In fact, I think that if I just show you the code here many of you will immediately see room for improvements. Please be so kind to tell me. Thanks!
#!/usr/bin/php -q
<?
// config
$timelimit = 180; // amount of seconds the server should run for, 0 = run indefintely
$address = $_SERVER['SERVER_ADDR']; // the server's external IP
$port = 9000; // the port to listen on
$backlog = SOMAXCONN; // the maximum of backlog incoming connections that will be queued for processing
// configure custom PHP settings
error_reporting(1); // report all errors
ini_set('display_errors', 1); // display all errors
set_time_limit($timelimit); // timeout after x seconds
ob_implicit_flush(); // results in a flush operation after every output call
//create master IPv4 based TCP socket
if (!($master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))) die("Could not create master socket, error: ".socket_strerror(socket_last_error()));
// set socket options (local addresses can be reused)
if (!socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1)) die("Could not set socket options, error: ".socket_strerror(socket_last_error()));
// bind to socket server
if (!socket_bind($master, $address, $port)) die("Could not bind to socket server, error: ".socket_strerror(socket_last_error()));
// start listening
if (!socket_listen($master, $backlog)) die("Could not start listening to socket, error: ".socket_strerror(socket_last_error()));
//display startup information
echo "[".date('Y-m-d H:i:s')."] SERVER CREATED (MAXCONN: ".SOMAXCONN.").\n"; //max connections is a kernel variable and can be adjusted with sysctl
echo "[".date('Y-m-d H:i:s')."] Listening on ".$address.":".$port.".\n";
$time = time(); //set startup timestamp
// init read sockets array
$read_sockets = array($master);
// continuously handle incoming socket messages, or close if time limit has been reached
while ((!$timelimit) or (time() - $time < $timelimit)) {
$changed_sockets = $read_sockets;
socket_select($changed_sockets, $write = null, $except = null, null);
foreach($changed_sockets as $socket) {
if ($socket == $master) {
if (($client = socket_accept($master)) < 0) {
echo "[".date('Y-m-d H:i:s')."] Socket_accept() failed, error: ".socket_strerror(socket_last_error())."\n";
continue;
} else {
array_push($read_sockets, $client);
echo "[".date('Y-m-d H:i:s')."] Client #".count($read_sockets)." connected (connections: ".count($read_sockets)."/".SOMAXCONN.")\n";
}
} else {
$data = #socket_read($socket, 1024, PHP_NORMAL_READ); //read a maximum of 1024 bytes until a new line has been sent
if ($data === false) { //the client disconnected
$index = array_search($socket, $read_sockets);
unset($read_sockets[$index]);
socket_close($socket);
echo "[".date('Y-m-d H:i:s')."] Client #".($index-1)." disconnected (connections: ".count($read_sockets)."/".SOMAXCONN.")\n";
} else {
if ($data = trim($data)) { //remove whitespace and continue only if the message is not empty
switch ($data) {
case "exit": //close connection when exit command is given
$index = array_search($socket, $read_sockets);
unset($read_sockets[$index]);
socket_close($socket);
echo "[".date('Y-m-d H:i:s')."] Client #".($index-1)." disconnected (connections: ".count($read_sockets)."/".SOMAXCONN.")\n";
break;
default: //for experimental purposes, write the given data back
socket_write($socket, "\n you wrote: ".$data);
}
}
}
}
}
}
socket_close($master); //close the socket
echo "[".date('Y-m-d H:i:s')."] SERVER CLOSED.\n";
?>
One small thing, personally i'd create a function for outputting instead of just using echo, that way its easy to turn it off, change the format etc.. eg
function log($message = '')
{
echo '['.date('Y-m-d H:i:s').']'.$message;
}
and then you can use :
log("SERVER CREATED (MAXCONN: ".SOMAXCONN.").\n");
instead of
echo "[".date('Y-m-d H:i:s')."] SERVER CREATED (MAXCONN: ".SOMAXCONN.").\n";
Oh and be sure to use === instead of == otherwise you might get some odd results.
I'd move your switch $data into a function as that will likely expand.
Also since it looks like you're using a text based protocol, might want to explode/strtok to get the first level command and check it against an array of valid commands. Could also have an array that describes what internal function to call and use call_user_func_array to dispatch the call.

Categories