How to create a socket server in PHP

EDIT: If you need to submit bugs or want to improve my work, its now available on github!
Ever tried searching for information on how to properly create a multi-client socket server in PHP? You’ll get plenty of results with outdated and messy source code, some of which won’t even work.
This was the my state a couple days ago when I decided that I wanted to build an IRC server. The why is not important… (For the fun of building an IRC Server). So I googled around a hell of a lot bit until I finally found some code that worked on its own, and quickly built a semi-functional IRC server using it, and headed off to sleep at 5am.
The next day I was very, very happy with the results of my hard labor, but it wasn’t good enough, so I started re-writing it from scratch as an Object, and thus I created class::IRCServer.
Today, once I felt that I was finished screwing around with my newly built IRCd, I decided to modify the function enough to be used on its own as a socket server, to share with the world.
And thus, class::SocketServer was created.
< ?php /*! @class SocketServer @author Navarr Barnier @abstract A Framework for creating a multi-client server using the PHP language. */ class SocketServer { /*! @var config @abstract Array - an array of configuration information used by the server. */ protected $config; /*! @var hooks @abstract Array - a dictionary of hooks and the callbacks attached to them. */ protected $hooks; /*! @var master_socket @abstract resource - The master socket used by the server. */ protected $master_socket; /*! @var max_clients @abstract unsigned int - The maximum number of clients allowed to connect. */ public $max_clients = 10; /*! @var max_read @abstract unsigned int - The maximum number of bytes to read from a socket at a single time. */ public $max_read = 1024; /*! @var clients @abstract Array - an array of connected clients. */ public $clients; /*! @function __construct @abstract Creates the socket and starts listening to it. @param string - IP Address to bind to, NULL for default. @param int - Port to bind to @result void */ public function __construct($bind_ip,$port) { set_time_limit(0); $this->hooks = array(); $this->config["ip"] = $bind_ip; $this->config["port"] = $port; $this->master_socket = socket_create(AF_INET, SOCK_STREAM, 0); socket_bind($this->master_socket,$this->config["ip"],$this->config["port"]) or die("Issue Binding"); socket_getsockname($this->master_socket,$bind_ip,$port); socket_listen($this->master_socket); SocketServer::debug("Listenting for connections on {$bind_ip}:{$port}"); } /*! @function hook @abstract Adds a function to be called whenever a certain action happens. Can be extended in your implementation. @param string - Command @param callback- Function to Call. @see unhook @see trigger_hooks @result void */ public function hook($command,$function) { $command = strtoupper($command); if(!isset($this->hooks[$command])) { $this->hooks[$command] = array(); } $k = array_search($function,$this->hooks[$command]); if($k === FALSE) { $this->hooks[$command][] = $function; } } /*! @function unhook @abstract Deletes a function from the call list for a certain action. Can be extended in your implementation. @param string - Command @param callback- Function to Delete from Call List @see hook @see trigger_hooks @result void */ public function unhook($command = NULL,$function) { $command = strtoupper($command); if($command !== NULL) { $k = array_search($function,$this->hooks[$command]); if($k !== FALSE) { unset($this->hooks[$command][$k]); } } else { $k = array_search($this->user_funcs,$function); if($k !== FALSE) { unset($this->user_funcs[$k]); } } } /*! @function loop_once @abstract Runs the class's actions once. @discussion Should only be used if you want to run additional checks during server operation. Otherwise, use infinite_loop() @param void @see infinite_loop @result bool - True */ public function loop_once() { // Setup Clients Listen Socket For Reading $read[0] = $this->master_socket; for($i = 0; $i < $this->max_clients; $i++) { if(isset($this->clients[$i])) { $read[$i + 1] = $this->clients[$i]->socket; } } // Set up a blocking call to socket_select if(socket_select($read,$write = NULL, $except = NULL, $tv_sec = 5) < 1) { // SocketServer::debug("Problem blocking socket_select?"); return true; } // Handle new Connections if(in_array($this->master_socket, $read)) { for($i = 0; $i < $this->max_clients; $i++) { if(empty($this->clients[$i])) { $temp_sock = $this->master_socket; $this->clients[$i] = new SocketServerClient($this->master_socket,$i); $this->trigger_hooks("CONNECT",$this->clients[$i],""); break; } elseif($i == ($this->max_clients-1)) { SocketServer::debug("Too many clients... "); } } } // Handle Input for($i = 0; $i < $this->max_clients; $i++) // for each client { if(isset($this->clients[$i])) { if(in_array($this->clients[$i]->socket, $read)) { $input = socket_read($this->clients[$i]->socket, $this->max_read); if($input == null) { $this->disconnect($i); } else { SocketServer::debug("{$i}@{$this->clients[$i]->ip} --> {$input}"); $this->trigger_hooks("INPUT",$this->clients[$i],$input); } } } } return true; } /*! @function disconnect @abstract Disconnects a client from the server. @param int - Index of the client to disconnect. @param string - Message to send to the hooks @result void */ public function disconnect($client_index,$message = "") { $i = $client_index; SocketServer::debug("Client {$i} from {$this->clients[$i]->ip} Disconnecting"); $this->trigger_hooks("DISCONNECT",$this->clients[$i],$message); $this->clients[$i]->destroy(); unset($this->clients[$i]); } /*! @function trigger_hooks @abstract Triggers Hooks for a certain command. @param string - Command who's hooks you want to trigger. @param object - The client who activated this command. @param string - The input from the client, or a message to be sent to the hooks. @result void */ public function trigger_hooks($command,&$client,$input) { if(isset($this->hooks[$command])) { foreach($this->hooks[$command] as $function) { SocketServer::debug("Triggering Hook '{$function}' for '{$command}'"); $continue = call_user_func($function,$this,$client,$input); if($continue === FALSE) { break; } } } } /*! @function infinite_loop @abstract Runs the server code until the server is shut down. @see loop_once @param void @result void */ public function infinite_loop() { $test = true; do { $test = $this->loop_once(); } while($test); } /*! @function debug @static @abstract Outputs Text directly. @discussion Yeah, should probably make a way to turn this off. @param string - Text to Output @result void */ public static function debug($text) { echo("{$text}\r\n"); } /*! @function socket_write_smart @static @abstract Writes data to the socket, including the length of the data, and ends it with a CRLF unless specified. @discussion It is perfectly valid for socket_write_smart to return zero which means no bytes have been written. Be sure to use the === operator to check for FALSE in case of an error. @param resource- Socket Instance @param string - Data to write to the socket. @param string - Data to end the line with. Specify a "" if you don't want a line end sent. @result mixed - Returns the number of bytes successfully written to the socket or FALSE on failure. The error code can be retrieved with socket_last_error(). This code may be passed to socket_strerror() to get a textual explanation of the error. */ public static function socket_write_smart(&$sock,$string,$crlf = "\r\n") { SocketServer::debug("< -- {$string}"); if($crlf) { $string = "{$string}{$crlf}"; } return socket_write($sock,$string,strlen($string)); } /*! @function __get @abstract Magic Method used for allowing the reading of protected variables. @discussion You never need to use this method, simply calling $server->variable works because of this method's existence. @param string - Variable to retrieve @result mixed - Returns the reference to the variable called. */ function &__get($name) { return $this->{$name}; } } /*! @class SocketServerClient @author Navarr Barnier @abstract A Client Instance for use with SocketServer */ class SocketServerClient { /*! @var socket @abstract resource - The client's socket resource, for sending and receiving data with. */ protected $socket; /*! @var ip @abstract string - The client's IP address, as seen by the server. */ protected $ip; /*! @var hostname @abstract string - The client's hostname, as seen by the server. @discussion This variable is only set after calling lookup_hostname, as hostname lookups can take up a decent amount of time. @see lookup_hostname */ protected $hostname; /*! @var server_clients_index @abstract int - The index of this client in the SocketServer's client array. */ protected $server_clients_index; /*! @function __construct @param resource- The resource of the socket the client is connecting by, generally the master socket. @param int - The Index in the Server's client array. @result void */ public function __construct(&$socket,$i) { $this->server_clients_index = $i; $this->socket = socket_accept($socket) or die("Failed to Accept"); SocketServer::debug("New Client Connected"); socket_getpeername($this->socket,$ip); $this->ip = $ip; } /*! @function lookup_hostname @abstract Searches for the user's hostname and stores the result to hostname. @see hostname @param void @result string - The hostname on success or the IP address on failure. */ public function lookup_hostname() { $this->hostname = gethostbyaddr($this->ip); return $this->hostname; } /*! @function destroy @abstract Closes the socket. Thats pretty much it. @param void @result void */ public function destroy() { socket_close($this->socket); } function &__get($name) { return $this->{$name}; } function __isset($name) { return isset($this->{$name}); } }
class::SocketServer does all the functions necessary for a server. It binds to the IP address and starts listening to the port. Its easy to specify a maximum number of clients to allow, and the way its coded makes it easily modified.
Here is an example of a server (using this class) that listens for a user to send a string, and then echoes the reverse of that string back to the user.
< ?php // This is PHP5 Code, by the way. require_once("SocketServer.class.php"); // Include the Class File $server = new SocketServer(null,31337); // Create a Server binding to the default IP address (null) and listen to port 31337 for connections $server->max_clients = 10; // Allow no more than 10 people to connect at a time $server->hook("CONNECT","handle_connect"); // Run handle_connect everytime someone connects $server->hook("INPUT","handle_input"); // Run handle_input whenever text is sent to the server $server->infinite_loop(); // Run Server Code Until Process is terminated. /* * All hooked functions are sent the parameters $server (The server class), $client (the connection), and $input (anything sent, if anything was sent) * You should save the variables $server and $client using an ampersand (&) to make sure they are references to the objects and not duplications. */ function handle_connect(&$server,&$client,$input) { SocketServer::socket_write_smart($client->socket,"String? ",""); // Outputs 'String? ' without a Line Ending } function handle_input(&$server,&$client,$input) { $trim = trim($input); // Trim the input, Remove Line Endings and Extra Whitespace. if(strtolower($trim) == "quit") // User Wants to quit the server { SocketServer::socket_write_smart($client->socket,"Oh... Goodbye..."); // Give the user a sad goodbye message, meany! $server->disconnect($client->server_clients_index); // Disconnect this client. return; // Ends the function } $output = strrev($trim); // Reverse the String SocketServer::socket_write_smart($client->socket,$output); // Send the Client back the String SocketServer::socket_write_smart($client->socket,"String? ",""); // Request Another String }
In essence, this class allows you to handle sockets in PHP. Beautifully handle sockets in PHP, that is.
Magical Typesetting in PHP
So, well working for Route 50 I came up with a fantastic idea for “typesetting” that well exceeded the norm. Something we constantly have issues with is what type of string was sent to MySQL originally (some of us have different conventional ideas about where escaping HTML should be located.) as well as outputting that string in its correct format.
Me, with my fantastic idea, came up with a couple variables classes that I put in a file named class_typesetting.php. The version on gist.github is slightly modified from the original version on the server.
It creates three classes, GenericVariable, String, and Number. So far we haven’t used GenericVariable, but since the introduction of the classes I’ve taken it upon myself to introduce them to any new code I write. When we create Core v5 (which will Objectify everything) strings taken from SQL will automatically be re-stored as String class variables.
First, lets examine some useful functionality.
<?php $title = new String($_POST[“title”]); // <strong>Hello 'World'</strong> ?> HTML Output: <?= $title->html ?> (<strong>Hello 'World'</strong>) Text Output: <?= $title->text ?> (Hello 'World') SQL Output: <?= $title->sql ?> (<strong>Hello \'World\'</strong>) HTML Attribute Output: <?= $title->html_attr ?>(<strong>Hello 'World'</strong>)
This allows for quick and easy access to the variables without having to worry about escaping them.
I recommend you hit the download link (class_typesetting.php) and play around with it. Tell me about anything that’s not working correctly and if possible implement it in your future code. (This means I’m putting this code in the “Public Domain”).
Closing Desktop Notifications on a Timer
So, one of the few things about Chrome’s desktop notifications I’ve been trying to figure out is how to close them on a timer, and it finally came to me.
This timer will only activate when the notification is opened, and will close the notification even if the page that originally spawned it has been closed.
Create and Show a new HTMLNotification.
<!DOCTYPE html> <html> <head> <title>Notification Title</title> <style type="text/css"> body,body* { font-family: sans-serif; font-size: 10pt; } h1 { font-family: sans-serif; margin: 0; padding: 0; font-size: 12pt; font-weight: bold; } p { margin: 0; padding: 0; } </style> </head> <body onload="setTimeout(self.close,5000)"> <h1>Notification Title</h1> <p>Notification Text</p> </body> </html>
That will close the window after five seconds and will look just like a normal notification!
What I learned is: Notification Windows respond to the javascript method self.close()
Open Letter to Dave Winer
I know you invented RSS and all that, but please, please stop raping your feed.
I do not like getting this every couple of weeks, or days.. I don’t know, I have no concept of time, but this isn’t the first time its happened.
Please?
Twitter Annotation Proposal – Image
This is a proposal for a twitter annotation that I hope to be blessed by both the development community and twitter itself.
Image Namespace
Images are something commonly shared throughout twitter. Something that would be fantastically idealistic would be embedding image data into twitter annotations.
Due to constraints, images should be resized to a lower-quality “preview”. Possibly VGA or so? A link to a privately hosted image should also be available.
Using the current preview information supplied by the twitter development team, I imagine that it would go something like this:
"image": { "preview": "data here.. base64 etc.", "preview_encode": "base64", // Or whatever else floats your boat. "bin" or "binary" for raw data. "full_src": "http://example.org/link/to/image.jpg" }
Any additional thoughts on other information that should be included? Maybe a title and description, or is that not needed? Please, leave all of your thoughts and ideas in the comments!
Using Google Voice with Outlook’s Dialer
Microsoft Outlook has this very nifty feature where you can connect your computer to the phone line and use your Outlook Contact List to instantly dial someone’s number. Of course, when they created this they needed to add support for using a calling card, as long distance in the same country hadn’t even begun to be free.
Now if you use Google Voice, you can use this to your advantage with the simple addition of just a few seconds to the call.
Continue Reading for Instructions on how to Outlook up to dial through Google Voice
Features Wanted in Skype
I realized there are some neat little features I would love to see in Skype (while playing around with Microsoft Outlook and getting it to call out using Google Voice, I’ll blog about that next).
Calls Out Using Modem
Doesn’t Skype do some fantastic Skype-Out thing where you can even use a specific number as a calling card and call out using it? A whole crapload of notebooks and desktops still ship with modems, why not utilize it! If you don’t have a connection to the net (or you’re just crazy), you could call out using a Skype-Out Call-In Number! Wouldn’t that be AWESOME?
Calls Out with Calling Card through Modem
Yeah, yeah, companies don’t like competition or whatnot, but if the previous is supported, why not spice it up a little and allow us to use a different calling card through the settings? That would allow Google Voice users to make outbound calls using Google Voice through Skype, and that would just be AWESOME.
Answering Calls Through Modem
Is your phone ringing? Yeah, don’t you wish you could pick it up using Skype? All it has to do is learn to speak through the modem and BABOOSH, you can now answer your landline ON SKYPE (GOOGLE VOICE MAKES THIS MORE AWESOME).
Contact Synchronization with Google
Come ON. EVERYTHING needs this.
Why SuperTweets won’t kill URL Shorteners
Now, the title of this blog post makes it sound like I’m going to write an essay about why SuperTweets (and the probability of them having a URL metadata for tweets) will not be killing URL Shorteners like j.mp and bit.ly anytime soon.
1) URL Shorteners Keep You Safe
One of the things URL shorteners do now is they keep you safe from malicious websites. You can preview the site you are visiting, and if its determined to be delicious the short URL will either be deleted or blocked or a warning will be shown, letting you know it is no longer safe to visit that URL.
2) URL Shorteners are Branded
All I really have to say here is “Bit.ly Pro” URL Shorteners now have custom branding, so it makes it even easier to send people to your website or promote your brand on twitter by including the link in the text.
3) URL Shorteners are Easy to Remember
When they are used correctly. Services like bit.ly allow you to give a custom name to your short link. This is especially useful in media such as Television or Print: http://j.mp/cnn-transgender is much easier to remember and type than www.cnn.com/2010/LIVING/04/14/transgender.irpt/index.html?hpt=C1
With even just these three simple reasons in mind, it is very clear to me that URL shorteners will not be dying anytime soon, no matter how much metadata you can attach to tweets. I’m not even sure they’re bad for the internet, anymore.


