[TASK] Added function for clients, so they're able to send initial data right after connecting to a server.

[TASK] Restructured parts of the core to support sending data right after connecting.
[TASK] Adapted core part for automatic reconnect to support sending initial data, too.
This commit is contained in:
Jan Philipp Timme 2011-06-25 22:13:53 +00:00
parent 4c9f5ba7f3
commit 2c5e520c9f
11 changed files with 159 additions and 66 deletions

View File

@ -82,6 +82,15 @@ abstract class Client_AbstractClient {
} }
/**
* This function gets called every time, the connection is established.
* This allows the client to send initial data.
* @return void
*/
public function initializeConnection() {
}
/** /**
* @param int $id * @param int $id
* @return void * @return void

View File

@ -17,25 +17,31 @@ class Client_BotClient extends Client_AbstractClient {
/** /**
* Does all the hard work. * Does all the hard work.
* @param string $data * @param object $contentObject
* @return string * @return void
*/ */
public function processContentObject($contentObject) { public function processContentObject($contentObject) {
$return = ""; $return = "";
$data = $contentObject->getRawData(); $rawData = $contentObject->getRawData();
if(strpos($data, "-") !== FALSE ) {
$data = explode("-", $data, 2); if(trim($rawData) === "list") {
$return = print_r(array("IDs" => $this->clientManager->getIDs(), "Groups" => $this->clientManager->getGroups()), TRUE);
}
if(strpos($rawData, "-") !== FALSE ) {
$data = explode("-", $rawData, 2);
$this->clientManager->sendToID((int) $data[0], $data[1]."\r\n"); $this->clientManager->sendToID((int) $data[0], $data[1]."\r\n");
$return = print_r($data, TRUE); $return = print_r($data, TRUE);
} }
if(strpos($data, "+") !== FALSE ) { if(strpos($rawData, "+") !== FALSE ) {
$data = explode("+", $data, 2); $data = explode("+", $rawData, 2);
$this->clientManager->sendToGroup($data[0], $data[1]."\r\n"); $this->clientManager->sendToGroup($data[0], $data[1]."\r\n");
$return = print_r($data, TRUE); $return = print_r($data, TRUE);
} }
//TODO: implement this correctly //TODO: implement this correctly
$return = str_replace("\n", "\r\n", $return);
$this->protocolHandler->sendRaw($return); $this->protocolHandler->sendRaw($return);
} }

View File

@ -84,6 +84,20 @@ class Client_ClientManager {
* @return void * @return void
*/ */
public function work() { public function work() {
//firstly, create clients for connections without one.
foreach($this->connectionPool->getConnectionHandlers() AS $connectionHandler) {
if(!isset($this->clientPool[$connectionHandler->getID()])) {
//new connections might need a client, so we'll create one here.
$this->addClientForConnectionHandler($connectionHandler);
//allow client to send initial stuff right after connecting to the server.
$this->clientPool[$connectionHandler->getID()]->initializeConnection();
//after initializing, have it process an empty string in order to get stuff to write. >.<
$result = $this->clientPool[$connectionHandler->getID()]->processRawData("");
if($result !== "") $connectionHandler->write($result);
}
}
//then, process all connections that have stuff to read.
$connectionHandlers = $this->connectionPool->select(); $connectionHandlers = $this->connectionPool->select();
if(isset($connectionHandlers["read"]) === FALSE || count($connectionHandlers["read"]) === 0) return; if(isset($connectionHandlers["read"]) === FALSE || count($connectionHandlers["read"]) === 0) return;
foreach($connectionHandlers["read"] AS $connectionHandler) { foreach($connectionHandlers["read"] AS $connectionHandler) {
@ -103,19 +117,23 @@ class Client_ClientManager {
$this->configPool[$newConnectionHandler->getID()] = $config; $this->configPool[$newConnectionHandler->getID()] = $config;
//remove old connection //remove old connection
$this->removeConnection($connectionHandler); $this->removeConnection($connectionHandler);
//re-initialize the client
$this->clientPool[$newConnectionHandler->getID()]->initializeConnection();
//and of course, process stuff to get the buffer filled. :/
$result = $this->clientPool[$newConnectionHandler->getID()]->processRawData("");
if($result !== "") $newConnectionHandler->write($result);
} }
$this->removeClientForConnectionHandler($connectionHandler); $this->removeClientForConnectionHandler($connectionHandler);
//reconnect or not - this connectionHandler won't do that much. //this connectionHandler won't do much anymore - kill it.
continue; continue;
} }
//handle accepted sockets, adopt them and treat them with care :-) //handle accepted sockets, adopt them and treat them with care :-)
if($connectionHandler->isListening() === TRUE) { if($connectionHandler->isListening() === TRUE) {
$this->addClientForConnectionHandler($connectionHandler); $this->addClientForConnectionHandler($connectionHandler);
//TODO: maybe we want to trigger the "client" here, too? -- YES
$this->clientPool[$connectionHandler->getID()]->initializeConnection();
} }
//create a client for a connection without one.
if(!isset($this->clientPool[$connectionHandler->getID()])) {
$this->addClientForConnectionHandler($connectionHandler);
}
//call the registered client //call the registered client
if(isset($this->clientPool[$connectionHandler->getID()])) { if(isset($this->clientPool[$connectionHandler->getID()])) {
//let the client process the data, send the results back. //let the client process the data, send the results back.
@ -127,6 +145,7 @@ class Client_ClientManager {
} }
} }
} }
//after that, we're done here.
} }
/** /**
@ -247,6 +266,30 @@ class Client_ClientManager {
return $this->connectionPool->writeToID($id, $data); return $this->connectionPool->writeToID($id, $data);
} }
/**
* Returns a list of available connection IDs.
* @return array
*/
public function getIDs() {
$list = array();
foreach($this->connectionPool->getConnectionHandlers() AS $connectionHandler) {
$list[] = $connectionHandler->getID();
}
return $list;
}
/**
* Returns a list of available connection groups.
* @return array
*/
public function getGroups() {
$list = array();
foreach($this->connectionPool->getConnectionHandlers() AS $connectionHandler) {
$list[] = $connectionHandler->getGroup();
}
return $list;
}
/** /**
* Calls Connection_Pool * Calls Connection_Pool
* @see Connection_ConnectionPool * @see Connection_ConnectionPool

View File

@ -10,12 +10,12 @@ class Client_IrcClient extends Client_AbstractClient {
/** /**
* @var boolean * @var boolean
*/ */
protected $got_001; protected $joined;
/** /**
* @var boolean * @var boolean
*/ */
protected $joined; protected $got001;
/** /**
* @var boolean * @var boolean
@ -53,44 +53,52 @@ class Client_IrcClient extends Client_AbstractClient {
*/ */
public function resetConnectionStatus() { public function resetConnectionStatus() {
$this->lines = 0; $this->lines = 0;
$this->got_001 = FALSE; $this->got001 = FALSE;
$this->joined = FALSE; $this->joined = FALSE;
$this->authed = FALSE; $this->authed = FALSE;
} }
/**
* This function gets called every time, the connection is established.
* This allows the client to send initial data.
* @return void
*/
public function initializeConnection() {
if(!$this->authed) {
$data = "USER poweruser as as :JPTs Bot\r\nNICK :" . $this->nick . "\r\n";
$this->authed = TRUE;
}
$this->protocolHandler->sendRaw($data);
}
/** /**
* Processes the resulting ContentObject from a ProtocolHandler. * Processes the resulting ContentObject from a ProtocolHandler.
* Does all the hard work. * Does all the hard work.
* @param string $data * @param string $data
* @return void * @return void
*/ */
public function processContentObject($contentObject) { protected function processContentObject($contentObject) {
$data = $contentObject->getRawData(); $data = $contentObject->getRawData();
//DEBUG //DEBUG
//var_dump($contentObject); //var_dump($contentObject);
//echo "[RECV] ".$data;
//respond to pings //respond to pings
if($contentObject->getCommand() === "PING") $this->protocolHandler->pong($contentObject->getParams()); if($contentObject->getCommand() === "PING") $this->protocolHandler->pong($contentObject->getParams());
$this->clientManager->sendToGroup("srv", "[#".$this->ID."] ".$data."\r\n\r\n"); $this->clientManager->sendToGroup("srv", "[#".$this->ID."] ".$data);
if($contentObject->getCommand() === "001") $this->got001 = TRUE;
$return = ""; $return = "";
if(preg_match("/001/", $data)) $this->got_001 = TRUE;
$this->lines++; $this->lines++;
if(!$this->authed && $this->lines > 1) { if(!$this->joined && $this->got001) {
$return .= "USER as as as :Asdfg\r\nNICK :" . $this->nick . "\r\n";
$this->authed = TRUE;
}
if($this->got_001 && !$this->joined) {
$this->joined = TRUE; $this->joined = TRUE;
foreach($this->channels AS $channel) $return .= "JOIN " . $channel . "\r\n"; foreach($this->channels AS $channel) $return .= "JOIN " . $channel . "\r\n";
} }
if(strpos($data, "hau ab") !== FALSE) { if(strpos($data, "multivitamin") !== FALSE) {
$return .= "PRIVMSG ".$this->channels[0]." :roger that :D\r\n"; $return .= "PRIVMSG ".$this->channels[0]." :roger that :D\r\n";
$return .= "QUIT :lol\r\n"; $return .= "QUIT :lol\r\n";
} }

View File

@ -207,7 +207,10 @@ class Connection_ConnectionHandler {
* @return boolean * @return boolean
*/ */
public function writeFromBuffer() { public function writeFromBuffer() {
$result = $this->socketHandler->write($this->buffer_outgoing->getAllBufferContents()); $bufferContent = $this->buffer_outgoing->getAllBufferContents();
//this might not be cool, but it should do.
if($bufferContent === "") return TRUE;
$result = $this->socketHandler->write($bufferContent);
if($result === FALSE) { if($result === FALSE) {
$this->shutdown(); $this->shutdown();
return FALSE; return FALSE;

View File

@ -56,6 +56,14 @@ class Connection_ConnectionPool {
return $connectionHandler; return $connectionHandler;
} }
/**
* Returns the array with the current connectionHandlers.
* @return array
*/
public function getConnectionHandlers() {
return $this->connectionHandlers;
}
/** /**
* Adds a ConnectionHandler to the pool. * Adds a ConnectionHandler to the pool.
* @param Connection_ConnectionHandler $add_connectionHandler * @param Connection_ConnectionHandler $add_connectionHandler
@ -108,9 +116,15 @@ class Connection_ConnectionPool {
foreach($this->connectionHandlers AS $connectionHandler) { foreach($this->connectionHandlers AS $connectionHandler) {
$connectionSocket = $connectionHandler->getSocket(); $connectionSocket = $connectionHandler->getSocket();
$read[] = $connectionSocket; $read[] = $connectionSocket;
if($connectionHandler->canWrite() && $connectionHandler->isServer() === FALSE) $write[] = $connectionSocket; if($connectionHandler->canWrite() === TRUE && $connectionHandler->isServer() === FALSE) {
$write[] = $connectionSocket;
//the line above does not work for freshly connected stuff.
//this is the fallback(?) - just write the stuff - no matter what happens.
if($connectionHandler->writeFromBuffer() === FALSE) $this->removeConnectionHandler($connectionHandler);
}
} }
$except = $read; $except = $read;
//Arrays are prepared, let's have socket_select() take a look and process its results. //Arrays are prepared, let's have socket_select() take a look and process its results.
$tempArray = array(); $tempArray = array();
$selectedSockets = $this->socketPool->select($read, $write, $except); $selectedSockets = $this->socketPool->select($read, $write, $except);
@ -129,6 +143,8 @@ class Connection_ConnectionPool {
} }
break; break;
case "write": case "write":
//this might still work on active connections that are "in use".
//however, it does not for freshly connected ones.
if($connectionHandler->writeFromBuffer() === FALSE) $this->removeConnectionHandler($connectionHandler); if($connectionHandler->writeFromBuffer() === FALSE) $this->removeConnectionHandler($connectionHandler);
break; break;
case "except": case "except":

View File

@ -6,15 +6,20 @@
*/ */
class Protocol_IrcProtocolHandler extends Protocol_AbstractProtocolHandler { class Protocol_IrcProtocolHandler extends Protocol_AbstractProtocolHandler {
/**
* @var string
*/
protected $linebreak;
/** /**
* Is called by the constructor. * Is called by the constructor.
* Shall create the two buffers and set them up. * Shall create the two buffers and set them up.
* @return void * @return void
*/ */
public function createBuffers() { public function createBuffers() {
$linebreak = "\r\n"; $this->linebreak = "\n";
$this->buffer_incoming = new Misc_Buffer($linebreak); $this->buffer_incoming = new Misc_Buffer($this->linebreak);
$this->buffer_outgoing = new Misc_Buffer($linebreak); $this->buffer_outgoing = new Misc_Buffer($this->linebreak);
} }
/** /**
@ -153,7 +158,7 @@ class Protocol_IrcProtocolHandler extends Protocol_AbstractProtocolHandler {
//break; //break;
//tell when stuff is not implemented //tell when stuff is not implemented
default: default:
echo "N.i.y.: " . $command . " [".$data."]\r\n"; //echo "N.i.y.: " . $command . " [".$data."]\r\n";
break; break;
} }
@ -182,7 +187,7 @@ class Protocol_IrcProtocolHandler extends Protocol_AbstractProtocolHandler {
* @return void * @return void
*/ */
public function sendRaw($data) { public function sendRaw($data) {
$this->buffer_outgoing->addData($data); $this->buffer_outgoing->addData($data . $this->linebreak);
} }
/** /**

View File

@ -16,17 +16,17 @@ class Socket_SocketHandler {
/** /**
* @var boolean * @var boolean
*/ */
protected $is_connected; protected $isConnected;
/** /**
* @var boolean * @var boolean
*/ */
protected $is_bound; protected $isBound;
/** /**
* @var boolean * @var boolean
*/ */
protected $is_listening; protected $isListening;
/** /**
* Default constructor. Sets the socket. * Default constructor. Sets the socket.
@ -37,9 +37,9 @@ class Socket_SocketHandler {
public function __construct($socket) { public function __construct($socket) {
if(is_resource($socket) === FALSE) throw new Exception_SocketException("Invalid socket ressource!", 1289663108); if(is_resource($socket) === FALSE) throw new Exception_SocketException("Invalid socket ressource!", 1289663108);
$this->socket = $socket; $this->socket = $socket;
$this->is_bound = FALSE; $this->isBound = FALSE;
$this->is_connected = FALSE; $this->isConnected = FALSE;
$this->is_listening = FALSE; $this->isListening = FALSE;
} }
/** /**
@ -62,23 +62,23 @@ class Socket_SocketHandler {
* @return boolean * @return boolean
*/ */
public function isConnected() { public function isConnected() {
return $this->is_connected; return $this->isConnected;
} }
/** /**
* Sets is_connected-flag. * Sets isConnected-flag.
* @param boolean $connected * @param boolean $connected
* @return void * @return void
*/ */
public function setConnected($connected) { public function setConnected($connected) {
$this->is_connected = $connected; $this->isConnected = $connected;
} }
/** /**
* @return boolean * @return boolean
*/ */
public function isListening() { public function isListening() {
return $this->is_listening; return $this->isListening;
} }
/** /**
@ -89,10 +89,10 @@ class Socket_SocketHandler {
* @return void * @return void
*/ */
public function connect($address, $port) { public function connect($address, $port) {
if($this->is_connected === TRUE) throw new Exception_SocketException("Socket is already connected!", 1289663170); if($this->isConnected === TRUE) throw new Exception_SocketException("Socket is already connected!", 1289663170);
$result = socket_connect($this->socket, $address, $port); $result = socket_connect($this->socket, $address, $port);
if($result === FALSE) $this->error(); if($result === FALSE) $this->error();
$this->is_connected = TRUE; $this->isConnected = TRUE;
} }
/** /**
@ -103,12 +103,12 @@ class Socket_SocketHandler {
* @return void * @return void
*/ */
public function bind($address, $port) { public function bind($address, $port) {
if($this->is_bound === TRUE) throw new Exception_SocketException("Socket is already bound!", 1289663212); if($this->isBound === TRUE) throw new Exception_SocketException("Socket is already bound!", 1289663212);
$result = socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1); $result = socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
if($result === FALSE) $this->error(); if($result === FALSE) $this->error();
$result = socket_bind($this->socket, $address, $port); $result = socket_bind($this->socket, $address, $port);
if($result === FALSE) $this->error(); if($result === FALSE) $this->error();
$this->is_bound = TRUE; $this->isBound = TRUE;
} }
/** /**
@ -117,10 +117,10 @@ class Socket_SocketHandler {
* @return void * @return void
*/ */
public function listen() { public function listen() {
if($this->is_bound === FALSE) throw new Exception_SocketException("Cannot listen on unbound socket!", 1289663220); if($this->isBound === FALSE) throw new Exception_SocketException("Cannot listen on unbound socket!", 1289663220);
$result = socket_listen($this->socket); $result = socket_listen($this->socket);
if($result === FALSE) $this->error(); if($result === FALSE) $this->error();
$this->is_listening = TRUE; $this->isListening = TRUE;
} }
/** /**
@ -128,7 +128,7 @@ class Socket_SocketHandler {
* @return void * @return void
*/ */
public function hasBeenAccepted() { public function hasBeenAccepted() {
$this->is_connected = TRUE; $this->isConnected = TRUE;
} }
/** /**
@ -138,8 +138,8 @@ class Socket_SocketHandler {
* @return ressource * @return ressource
*/ */
public function accept() { public function accept() {
if($this->is_bound === FALSE) throw new Exception_SocketException("Cannot accept connections from unbound socket!", 1289663239); if($this->isBound === FALSE) throw new Exception_SocketException("Cannot accept connections from unbound socket!", 1289663239);
if($this->is_listening === FALSE) throw new Exception_SocketException("Cannot accept connections from socket that is not listening!", 1289663241); if($this->isListening === FALSE) throw new Exception_SocketException("Cannot accept connections from socket that is not listening!", 1289663241);
$accept = socket_accept($this->socket); $accept = socket_accept($this->socket);
if($accept === FALSE) $this->error(); if($accept === FALSE) $this->error();
return $accept; return $accept;
@ -151,7 +151,7 @@ class Socket_SocketHandler {
* @return string * @return string
*/ */
public function getRemoteName() { public function getRemoteName() {
if($this->is_connected === FALSE) throw new Exception_SocketException("Socket not connected, cannot retrieve remote name!", 1289928192); if($this->isConnected === FALSE) throw new Exception_SocketException("Socket not connected, cannot retrieve remote name!", 1289928192);
$result = socket_getpeername($this->socket, $address, $port); $result = socket_getpeername($this->socket, $address, $port);
if($result === FALSE) $this->error(); if($result === FALSE) $this->error();
return $address . ":" . $port; return $address . ":" . $port;
@ -163,7 +163,7 @@ class Socket_SocketHandler {
* @return string * @return string
*/ */
public function getLocalName() { public function getLocalName() {
if($this->is_bound === FALSE ^ $this->is_connected === FALSE) throw new Exception_SocketException("Socket is not bound or connected, no local name available!", 1289928256); if($this->isBound === FALSE ^ $this->isConnected === FALSE) throw new Exception_SocketException("Socket is not bound or connected, no local name available!", 1289928256);
$result = socket_getsockname($this->socket, $address, $port); $result = socket_getsockname($this->socket, $address, $port);
if($result === FALSE) $this->error(); if($result === FALSE) $this->error();
return $address . ":" . $port; return $address . ":" . $port;
@ -199,7 +199,7 @@ class Socket_SocketHandler {
*/ */
public function close() { public function close() {
usleep(100000); usleep(100000);
if($this->is_connected === TRUE) { if($this->isConnected === TRUE) {
$result = socket_shutdown($this->socket); $result = socket_shutdown($this->socket);
if($result === FALSE) $this->error(); if($result === FALSE) $this->error();
} }
@ -207,7 +207,7 @@ class Socket_SocketHandler {
$result = socket_close($this->socket); $result = socket_close($this->socket);
if($result === FALSE) $this->error(); if($result === FALSE) $this->error();
} }
$this->is_connected = FALSE; $this->isConnected = FALSE;
} }
/** /**

View File

@ -71,8 +71,8 @@ class Socket_SocketPool {
* @return ressource * @return ressource
*/ */
public function createTcpSocket($IPv6 = FALSE) { public function createTcpSocket($IPv6 = FALSE) {
$Domain = ($IPv6) ? AF_INET6 : AF_INET; $domain = ($IPv6) ? AF_INET6 : AF_INET;
$socket = socket_create($Domain, SOCK_STREAM, SOL_TCP); $socket = socket_create($domain, SOCK_STREAM, SOL_TCP);
socket_set_block($socket); socket_set_block($socket);
if($socket === FALSE) throw new Exception_SocketException("socket_create() failed!", 1290273709); if($socket === FALSE) throw new Exception_SocketException("socket_create() failed!", 1290273709);
$this->addSocket($socket); $this->addSocket($socket);
@ -90,7 +90,7 @@ class Socket_SocketPool {
*/ */
public function select($read, $write, $except, $timeout = NULL) { public function select($read, $write, $except, $timeout = NULL) {
$n = socket_select($read, $write, $except, $timeout); $n = socket_select($read, $write, $except, $timeout);
if($n === FALSE) throw new Exception_SocketException("socket_select() failed!", 1290273693); if($n === FALSE) throw new Exception_SocketException("socket_select() failed! - Error: " . socket_strerror(socket_last_error()), 1290273693);
if($n === 0) return array("read" => array(), "write" => array(), "except" => array()); if($n === 0) return array("read" => array(), "write" => array(), "except" => array());
return array("read" => $read, "write" => $write, "except" => $except); return array("read" => $read, "write" => $write, "except" => $except);
} }

View File

@ -6,6 +6,7 @@ try {
require_once('Testcode/Client/ClientManagerTest.php'); require_once('Testcode/Client/ClientManagerTest.php');
} }
catch(Exception $e) { catch(Exception $e) {
echo "\n\nCaught Exception: " . $e . "\n"; echo PHP_EOL.PHP_EOL."Caught Exception: " . $e . PHP_EOL;
} }
?> ?>

View File

@ -13,13 +13,15 @@ $freenode->connect("irc.freenode.net", 6667);
$freenode->setReconnect(TRUE); $freenode->setReconnect(TRUE);
*/ */
$freenode = $clientManager->createTcpConnection("freenode", "irc"); $euirc = $clientManager->createTcpConnection("euirc", "irc");
$clientManager->attachConfig(array( $clientManager->attachConfig(array(
"nick" => "Pb42", "nick" => "Pb42",
"userident" => "uzuguck", "userident" => "Serena",
"channels" => array("#mstasty", "#starsim") "channels" => array("#kuzuru-subs")
), $freenode); ), $euirc);
$freenode->connect("irc.freenode.net", 6667); $euirc->connect("irc.euirc.net", 6667);
$euirc->setReconnect(TRUE);
/*$config_eloxoph = array( /*$config_eloxoph = array(
"nick" => "Frischmilch", "nick" => "Frischmilch",