PHP

Yesterday I added the product of my weekend-of-hackery to PECL: the event extension.

PECL::event implements an event based scheduling engine that will execute a callback when a particular event is triggered. The various different triggers allowed are:

As you might have already guessed, PECL::event is probably most useful for longer-lived scripts that need to perform more than network "operation" without either taking too long (why do one after the other if you can do both at the same time?) or without one blocking the other and making it time out.

I've previously given a talk (or was it a magazine article?) on multiplexing with streams in PHP; this extension takes things a step further by taking the complex scheduling logic out of your script. As a bonus, it can also take advantage of the more scalable IO scheduling interfaces (epoll(4), kqueue, /dev/poll, poll(2) and select(2)) available on various different operating systems. Scalable is one of those phrases that can easily be misinterpreted, so in this context more scalable means lower overhead per file descriptor , which should translate to faster execution time in your script.

In practice, you probably won't notice much difference between the different engines in PECL::event, but you should notice the difference between a userspace implementation using stream_select() and PECL::event.

How do I use it?

<?php
  # our callback
  function readable($stream, $mask, $arg) {
     if ($mask & EV_READ) {
       echo "$arg is readable:\\n";
       echo fread($stream, 8192);
       fwrite($stream, "QUIT\\r\\n");
       fclose($stream);
     }
  }
  # detect and activate the best available scheduling engine  
  event_init();
  # create a new event
  $e = event_new(
       # associate it with an async connection to a gmail MX
       stream_socket_client("tcp://gsmtp171.google.com:25",
          $errno, $errstr, 0,
          STREAM_CLIENT_CONNECT|STREAM_CLIENT_ASYNC_CONNECT),
       # ask the engine to tell us when it is ready to read
       EV_READ,
       # tell it to call the 'readable' function when triggered
       'readable',
       # and pass in the following string as an argument
       'gmail mx');
  # put the event into the scheduling engine   
  event_schedule($e);
  # similarly, create another event, this time connecting
  # to the PHP MX 
  $e = event_new(
       stream_socket_client("tcp://osu1.php.net:25",
          $errno, $errstr, 0,
          STREAM_CLIENT_CONNECT|STREAM_CLIENT_ASYNC_CONNECT),
       EV_READ,
       'readable',
       'php mx');
  event_schedule($e);
  # now service all registered events
  event_dispatch();
  # we get here when both events have been run
?>

If you run this script, you should see it output something like this:

  gmail mx is readable:
  220 mx.gmail.com ESMTP 71si267099rna
  php mx is readable:
  220-PHP  is a widely-used general-purpose scripting language
  220-that is especially suited for Web development and can be
  220-embedded into HTML. If you are new to PHP and want to get
  220-some idea of how it works, try the introductory tutorial.
  220-After that, check out the online manual, and the example
  220-archive sites and some of the other resources available in
  220-the links section.
  220-
  220-Do not send UBE to this server.
  220-
  220 osu1.php.net ESMTP Ecelerity HEAD (r3928) Mon, 13 Dec 2004 17:59:20 -0800

You'll see the osu1 banner a couple of seconds after running the script, because that server deliberately pauses.

What's the point?

The script above connects to two SMTP servers, reads their banners and then gracefully closes the connection. So what? Big deal. I can write a much shorter script that does the same thing using just a couple of lines for each host:

<?php
   $fp = fsockopen('gsmtp171.google.com', 25);
   echo fgets($fp);
   fwrite($fp, "QUIT\\r\\n");
   fclose($fp);
   $fp = fsockopen('osu1.php.net', 25);
   echo fgets($fp);
   fwrite($fp, "QUIT\\r\\n");
   fclose($fp);
?>

That may be the case, but with the PECL::event approach, you're not blocking on each connection (remember, osu1 makes you wait a couple of seconds) and you're not blocking on each read. In fact, you could happily add 10s or perhaps even 100s of machines to monitor and have them all happen concurrently. Neat stuff, huh?

That's all I'm going to write for now, but before I go I'll leave you with a couple of interesting snippets if you're interested in playing with the extension.


View Comments