Need help in PHP with non-blocking sockets for IRC bot - php

I've been learning to write an IRC bot in PHP and it's been working great but the blocking has become an issue because I want certain actions to happen based on a timer. I've been trying to learn about non-blocking but im struggling to get it working correctly. Can anyone have a look at my code and let me know where im going wrong?
<?php
$ircPort = 6667;
$ircBotNick = "CaveBot";
$ircBotPrefix = "!";
$ircBotPass = "";
$ircBotIdent = "CaveBot";
$ircBotRealName = "Cavemans Bot Script";
$ircDefaultChannel = "#CaveDen";
$ircServer = "localhost";
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$error = socket_connect($socket, $ircServer, $ircPort);
socket_set_nonblock($socket);
while (1) {
$read = array($socket);
$write = NULL;
$except = NULL;
$num_changed_sockets = socket_select($read, $write, $except, 0);
if ($num_changed_sockets === false) {
// Error handling
} else if ($num_changed_sockets > 0) {
$data = socket_read($socket, 1024, PHP_NORMAL_READ);
echo $n . " - ";
$d = explode(' ', $data);
$d = array_pad($d, 10, '');
if ($d[0] === 'PING') {
socket_write($socket, "PONG $d[1]");
}
print_r($data);
}
}
I think i'm not handling the read/incoming data correctly. It starts to connect but freezes up after a message from the server about looking up hostname. When I use similar code that has blocking, i get a ping and welcome message after the lookup.

Related

stream_select timeout on newly opened socket

I use thrift php client to connect to java thrift server. But got the error of
TSocket: timed out writing 78 bytes from 10.0.1.151:1234
I dig into the php source of thrift and find it was caused from timeout on function stream_select.
/**
* Write to the socket.
*
* #param string $buf The data to write
*/
public function write($buf)
{
$null = null;
$write = array($this->handle_);
// keep writing until all the data has been written
while (TStringFuncFactory::create()->strlen($buf) > 0) {
// wait for stream to become available for writing
$writable = #stream_select($null, $write, $null, $this->sendTimeoutSec_, $this->sendTimeoutUsec_);
if ($writable > 0) {
// write buffer to stream
$written = #stream_socket_sendto($this->handle_, $buf);
if ($written === -1 || $written === false) {
throw new TTransportException('TSocket: Could not write '.TStringFuncFactory::create()->strlen($buf).' bytes '.
$this->host_.':'.$this->port_);
}
// determine how much of the buffer is left to write
$buf = TStringFuncFactory::create()->substr($buf, $written);
} elseif ($writable === 0) {
throw new TTransportException('TSocket: timed out writing '.TStringFuncFactory::create()->strlen($buf).' bytes from '.
$this->host_.':'.$this->port_);
} else {
throw new TTransportException('TSocket: Could not write '.TStringFuncFactory::create()->strlen($buf).' bytes '.
$this->host_.':'.$this->port_);
}
}
}
That means the socket was blocked and was not able to write. But the socket is just open and should not be blocked. I try select immediately after pfsockopen
if ($this->persist_) {
$this->handle_ = #pfsockopen($this->host_,
$this->port_,
$errno,
$errstr,
$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000));
} else {
$this->handle_ = #fsockopen($this->host_,
$this->port_,
$errno,
$errstr,
$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000));
}
// Connect failed?
if ($this->handle_ === FALSE) {
$error = 'TSocket: Could not connect to '.$this->host_.':'.$this->port_.' ('.$errstr.' ['.$errno.'])';
if ($this->debug_) {
call_user_func($this->debugHandler_, $error);
}
throw new TException($error);
}
$write = array($this->handle_);
$writable = #stream_select($null, $write, $null, $this->sendTimeoutSec_, $this->sendTimeoutUsec_);
if ($writable === 0) {
die('123');
}
The result show that it is block right after it is opened!
When restart php-fpm, the error disappear for a while and come up again.
Here is the client code:
$socket = new TSocket('10.0.1.151', 1234, true);
$framedSocket = new TFramedTransport($socket);
$transport = $framedSocket;
$protocol = new TCompactProtocol($transport);
$transport->open();
$client= new userservice\UserServiceProxyClient($protocol);
$result = $client->findUser($id);
If I adjust the php-fpm configuration pm.max_children from 200 to 2, the error also disappear.
I tried increase timeout to 10 seconds and it timeout after 10 seconds.
Any idea for what's the cause of the problem and how to fix it?
I modify the php thrift lib. Change from
$writable = #stream_select($null, $write, $null, $this->sendTimeoutSec_, $this->sendTimeoutUsec_);
to
$writable = 1;
And the same for read.
The problem get fixed.
That means the connection is established ( I even use tcpdump to confirm the tcp connection is established) and writable, but select timeout. Strange things!

PHP detecting TCP client disconnect

I got a TCP server up and running, which supports many client connections at the same time, together with reading and sending data. However, when client closes the connection, there is a delay on the server to detect a disconnected client.
// TCP socket created here
$clients = array($socket);
while (true) {
$read = $clients; // Copy of $clients
$write = NULL;
$except = NULL;
$num_changed_sockets = socket_select($read, $write, $except, NULL);
if ($num_changed_sockets === false) {
// ...
} else if ($num_changed_sockets > 0) { // Something changed
printb("In here."); // EDIT
if (in_array($socket, $read)) { // For new connections and removing host from the $clients copy
$newsock = socket_accept($socket);
if ($newsock !== false) {
printb("Client $newsock connected");
$clients[] = $newsock;
}
$key = array_search($socket, $read);
unset($read[$key]);
}
foreach ($read as $read_socket) { // The actual problem starts here?
printb("Read part."); // EDIT
$data = socket_read($read_socket, 128);
if ($data === false) {
printb("Client $read_socket disconnected");
$key = array_search($read_socket, $clients);
socket_close($clients[$key]);
unset($clients[$key]);
continue;
}
}
}
EDIT I:
I just added debug prints in the code. When a single client disconnects, server falls into infinite loop from $num_changed_sockets. Is there way to fix it?
EDIT II:
var_dump gives empty string after a client has disconnected.
EDIT III:
According to last edit, disconnecting the client, if socket_read returns an empty string seems to work. I Found a note from PHP's website:
Note: socket_read() returns a zero length string ("") when there is no more data to read.
Should not this return false, when client disconnects?

PHP multiple-connection socket (broadcasting issue)

What I'm trying to achieve is a socket server that broadcasts some data to all connected peers. This is code of server loop:
while(TRUE) {
$read = array();
$read[] = $socket;
for($i = 0; $i < $max_clients; $i++) {
if($client_sockets[$i] != NULL) {
$read[$i+1] = $client_sockets[$i];
}
}
#This is broadcasting loop
foreach($client_sockets as $send_sock)
{
socket_write($send_sock, "broadcasting".PHP_EOL);
}
if( socket_select($read, $write, $except, NULL) === FALSE ) {
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Could not listen on socket : [$errorcode] $errormsg \n");
}
if( in_array($socket, $read) ) {
for($i = 0; $i < $max_clients; $i++) {
if($client_sockets[$i] == NULL) {
$client_sockets[$i] = socket_accept($socket);
if( socket_getpeername($client_sockets[$i], $client_address, $client_port) ) {
echo "Client $client_address : $client_port is now connected to us. \n";
}
$message = "Connected to php socket server".PHP_EOL;
socket_write($client_sockets[$i], $message);
break;
}
}
}
}
This code works fine, accepts multiple connections and broadcasts data, except for one moment: loop starts only after I type any input from any connected client via telnet. I know this is because socket_select waits for this input, but I don't know how to start broadcasting right after client is connected.
Appreciate any help on this, because I've got feeling that I'm terribly wrong somewhere.
Selfown answer
Problem indeed was in socket_select($read, $write, $except, NULL); and not actually a problem. Last NULL parameter starts endless block, awaiting for client response; to make script work properly, I've changed this timeout, so it looks looks this: socket_select($read, $write, $except, 0, 500000). On the client side, however, parameter should be set to NULL(NOT 0!), so script remains idle while waiting for server broadcasting message.
I've also discovered that such blocking behavior is similar to to socket_read() and socket_write() functions, which endless by default; to change it, use socket_set_option:
socket_set_option($master_socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 0, "usec" => 1000));
socket_set_option($master_socket, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 0, "usec" => 1000));
And 1000 microseconds is as low as I could get.
Actually, I've updated answer for somebody who voted up; but I hope that solution will be useful anyway.

Query DHT Server

I'm trying to make a simple query to a DHT server.
I'm trying to make a simple simple example to test queries to the servers, but I don't get a response from the server in any form...
Little example:
$socket = fsockopen("udp://router.bittorrent.com", 6881, $errno, $errstr, 3);
fwrite($socket, 'p'); $Head = fread($socket, 4);
$CheckStatus = socket_get_status($socket);
if($CheckStatus["unread_bytes"] == 0)
{
return 0;
}
$do = 1;
while($do)
{
$str = fread($socket,1);
$stats .= $str;
$status = socket_get_status($socket);
if($status["unread_bytes"] == 0)
{
$do = 0;
}
}
fclose($socket);
The info about the queries in DHT server is here: http://www.bittorrent.org/beps/bep_0005.html#dht-queries
But I don't understand how make this with PHP.
Is this possible? What's the problem with my code?
As the8472 mentions, your client is sending p, which is not a valid query. To see valid query formats, look here: bep 005

How can I send data with PHP to an IP address via UDP?

How can I send data with PHP to an IP address via UDP?
How can I recive that data on the other computer?
<?php
$fp = pfsockopen( "udp://192.168.1.6", 9601, $errno, $errstr );
if (!$fp)
{
echo "ERROR: $errno - $errstr<br />\n";
}
socket_set_timeout ($fp, 10);
$write = fwrite( $fp, "kik" );
//$data .= fread($fp,9600);
//echo "$data<br>";
fclose($fp);
echo "<br>Connection closed ..<br>";
if (!$write) {
echo "error writing to port: 9600.<br/>";
next;
?>
This code sends the "kik" with a program I can read it on the another computer, but how can I see it in the browser?
My PHP knowledge is a bit rusty so I've been doing some searching trying to find some good guides and tutorials. This one PHP Sockets Made Easylooks like it will be a good starter guide for you.
Edit: The original article I posted did not go into great detail for UDP so I eliminated the previous code. The article from the PHP Manual has some more information specifically regarding UDP:
<?php
$socket = stream_socket_server("udp://127.0.0.1:1113", $errno, $errstr, STREAM_SERVER_BIND);
if (!$socket) {
die("$errstr ($errno)");
}
do {
$pkt = stream_socket_recvfrom($socket, 1, 0, $peer);
echo "$peer\n";
stream_socket_sendto($socket, date("D M j H:i:s Y\r\n"), 0, $peer);
} while ($pkt !== false);
?>
Edit #2: Here is another useful tutorial for socket programming in PHP. It is mostly TCP but it does include a section on how to alter the code to use UDP instead.
Just pulled this snippet out of some working code I have
if (!socket_bind($sh, LISTENIP, LISTENPORT)) exit("Could not bind to socket");
while (TRUE) {
// $z = socket_recvfrom($sh, $data, 65535, 0, $connectip, $connectPort);
while(socket_recvfrom($sh, $data, 65535, 0, $connectip, $connectPort)) {
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else { #START ELSE COULD FORK
$PIDS[$pid] = $pid; //KEEP TRACK OF SPAWNED PIDS
if ($pid) {
//PARENT THREAD : $ch is a copy that we don't need in this thread
} else {
/** CHILD THREAD::BEGIN PROCESSING THE CONNECTION HERE! **/
include "include/child_thread.inc.php";
} //Child Thread
}//if-else-forked
/** CLEANUP THE CHILD PIDs HERE :: "Any system resources used by the child are freed." **/
foreach ($PIDS as $pid) pcntl_waitpid($pid,$status,WNOHANG);
$i++; //INCREASE CONNECTION COUNTER
}//While socket_accept
/** CLEANUP THE PARENT PIDS **/
foreach ($PIDS as $pid) {
$returnPid = pcntl_waitpid($pid,$status);
unset($PIDS[$pid]);
}
}//While True
I think you'll find that the PHP's socket reference is a good place to study on this topic.
<?php
$server_ip = '127.0.0.1';
$server_port = 43278;
$beat_period = 5;
$message = 'PyHB';
print "Sending heartbeat to IP $server_ip, port $server_port\n";
print "press Ctrl-C to stop\n";
if ($socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) {
while (1) {
socket_sendto($socket, $message, strlen($message), 0, $server_ip, $server_port);
print "Time: " . date("%r") . "\n";
sleep($beat_period);
}
} else {
print("can't create socket\n");
}
?>

Categories