IPC: Unix sockets and PHP

Posted on July 21, 2016

Recently I’ve come across a use case for having PHP and Ruby talk to each other on the same server. While I knew that this should be possible through the use of UNIX sockets, I’ve never actually delved into rolling my own sockets. The PHP documentation around sockets is obtuse at best. Ruby’s UNIXSocket and UNIXServer classes are decently well documented, but there’s a dearth of examples on the internet. Here’s how I got it to work.

In this case, I have PHP as the server, and Ruby as the client. PHP starts listening, Ruby writes to PHP, and then PHP responds with some data.

Server (PHP)

Here’s the PHP:

<?php
$file_sock = dirname(__FILE__)."/server.sock";
 
$server = stream_socket_server('unix://'.$file_sock);
 
while(true) {
    $readSockets = $writeSockets = $errorSockets = [$server];
    $numChanged = stream_select($readSockets, $writeSockets, $errorSockets, null);
 
    foreach($readSockets as $sock) {
        $newSock = stream_socket_accept($sock);
        $data = stream_socket_recvfrom($newSock, 1500);
 
        echo "DATA: $data\n";
        stream_socket_shutdown($newSock, STREAM_SHUT_RDWR);
    }
}
 
unlink($file_sock);

A quick explanation of what’s going on here. The main part to understand is that stream_select operates and mutates arrays, so every time we loop through the while, the arrays get cleared out – so you need to recreate them every time. You also need to know that in this case, stream_select is a blocking action, and the rest of the while loop won’t fire until a change is detected on any of the open sockets. (I believe you can get it to be non-blocking via stream_set_blocking, but I’ve not tested that.)

So this starts by opening a socket stream on a file named server.sock in the current directory. It then jumps into a loop and waits for any changes on that socket. When data comes in, it iterates through each read socket. It accepts data into $newSock, reads from that, and then closes that new socket. Repeat ad infinitum, and delete server.sock when we’re done.

Now since sockets go in both directions (compare to pipes, which are one-way), you can write data into the socket after reading in. Add this before the shutdown line:

stream_socket_sendto($newSock, 'Hello, this is some test data.');

And you’re good to go.

Client (Ruby)

require 'socket'
 
s = UNIXSocket.new(Dir.pwd + "/server.sock")
s.send "I will now write some data.", 0
 
while true
  m = s.recvfrom(1500)
  break if m[0].empty?
  p m[0]
end

This part is easier. It opens a socket using the same path, sends some data through, and then receives data. The response from s.recvfrom looks like ["Data", ["AF_UNIX", ""]], so we want to keep receiving until the first parameter is empty (not nil), and then exit out.

That’s pretty much it. From here, you run the PHP script and let it run, and then invoke the Ruby script to write stuff to PHP and then receive a response. Looking at this it seems quite simple, but it was actually a few hours’ work to discern the PHP side of things. Hopefully this post helps someone struggling through the same issues.

Leave a Reply

Your email address will not be published. Required fields are marked *