Profile pic of Tommy KuTommy Ku's Blog

Reading and thinking

Communication between C++ and PHP program using socket

Posted on January 13, 2018

Background

In a recent job, I had to integrate a piece of hardware with my PHP server. The only library available was written in C and Java, yet it’s unreasonable to port the whole PHP application just because it lacks the library to communicate with the hardware.

Then I came up with an idea of writting a C++ application to bridge the hardware and the PHP application. The C++ application can use the library, no problem, but how could I let my C++ and my PHP applications communicate?

I chose to use socket because it’s straightforward to implement and reliable enough (for TCP) for my little application that exchanges only short texts per transaction.

In this short post, I will go over writing a C++ server and a PHP client that communicate using socket. Demo code can be found on tommyku/cpp-php-socket-demo.

C++ Server

Our C++ server is a simple echo server — it echos back to the client whatever it receives.

First, we need to include some libraries.

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <signal.h>

using namespace std;

Next I create a signal handler to handle SIGTERM, SIGKILL and SIGINT. This handler becomes useful when I ran my server as a daemon in the background because I can clean things up such as removing wthe socket file and flushing the log buffer here when the application closes. Note I made the socket file descriptor server global so that the signal handler can access it outside of the main function.

#define SOCKET_FILENAME "/tmp/server.sock"

int server;

void signal_callback_handler(int signum)
{
  // close server
  close(server);
  // remove the socket file
  unlink(SOCKET_FILENAME);
  // signal handled
  exit(0);
}

Here goes the rest of the application.

int main(int argc, char **argv)
{
  struct sockaddr_un server_addr, client_addr;
  socklen_t clientlen = sizeof(client_addr);
  int client, buflen, nread;
  char *buf;

  puts("Hell World");

  // listen to SIGINT, SIGTERM, and SIGKILL
  signal(SIGINT, signal_callback_handler);
  signal(SIGTERM, signal_callback_handler);
  signal(SIGKILL, signal_callback_handler);

  // setup socket address structure
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sun_family = AF_UNIX;
  strcpy(server_addr.sun_path, SOCKET_FILENAME);

  // create socket
  server = socket(PF_UNIX, SOCK_STREAM, 0);
  if (!server) {
    perror("socket");
    exit(-1);
  }

  // call bind to associate the socket with our local address and
  // port
  if (bind(server, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("bind");
    exit(-1);
  }

  // convert the socket to listen for incoming connections
  if (listen(server, 0) < 0) {
    perror("listen");
    exit(-1);
  }

  puts("Listening to connection...");

  // allocate buffer
  buflen = 1024;
  buf = new char[buflen+1];

  // loop to handle all requests
  while (1) {
    unsigned int client = accept(server, (struct sockaddr *)&client_addr, &clientlen);

    // read a request
    memset(buf, 0, buflen);
    nread = recv(client, buf, buflen, 0);

    printf("\nClient says: %s\n\n", buf);

    // echo back to the client
    send(client, buf, nread, 0);

    close(client);
  }

  close(server);

  unlink(SOCKET_FILENAME);

return 0;
}

At first you create a socket file descriptor with socket domain and type, which hasn’t been bind to anything yet. Then you bind it to a specific address described in server_addr. After that, you put the socket in passive mode which waits for clients to approach and make connection.

When creating a socket, I had to made a design decision whether to use UNIX domain or Internet domain when creating my socket.

UNIX domain (using PF_UNIX when creating socket) is a component of POSIX, so it’s internal of the host and does not require (de)encapsulation of the internet and network layer of TCP/IP. Therefore, it’s more efficient for IPC and more secure as other devices in the LAN cannot tap into this socket.

On the other hand, Internet domain is just like UNIX domain but the socket is binded to an address and a port instead of a socket file as in UNIX domain. It works like UNIX domain socket but other devices in the LAN can connect to this socket (depending on your firewall setting).

Since the purpose of this socket is purely for interprocess communication, there is no need to expose the port to outside devices, so I chose to create my socket in UNIX domain.

In the infinite while loop, the program accepts connection from a client, and use recv to read the content sent by the client. After doing something with the content received (in this program it does nothing), sent is used to send a reply to the client.

  while (1) {
    unsigned int client = accept(server, (struct sockaddr *)&client_addr, &clientlen);
    // rest of the code...

PHP Client

The PHP code does pretty much the similar thing. Except that instead of binding to a socket, it connects to a socket that’s already opened by the C++ program. It will send a message, wait for the first reply while the C++ program does it’s thing, and then terminate.

<?php
error_reporting(E_ALL);

if(!($sock = socket_create(AF_UNIX, SOCK_STREAM, 0)))
{
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);

    die("Couldn't create socket: [$errorcode] $errormsg \n");
}

echo "Socket created";

if(!socket_connect($sock , '/tmp/server.sock'))
{
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);
    die("Could not connect: [$errorcode] $errormsg \n");
}

echo "Connection established \n";

$message = $argv[1];

if(!socket_send( $sock , $message , strlen($message) , 0))
{
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);

    die("Could not send data: [$errorcode] $errormsg \n");
}

echo "Message send successfully \n";

// Now receive reply from server
if(socket_recv( $sock , $buf , 1024, MSG_WAITALL ) === FALSE)
{
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);
    die("Could not receive data: [$errorcode] $errormsg \n");
}

echo "Message received \n";

// print the received message
var_dump($buf);

socket_close($sock);

One thing to note if you’re like me who develops the PHP application in a docker container which has a separate file system from the host is that you need to mount the directory where your socket file resides in the host into the docker container. Otherwise, you may scratch your head like I did when socket_connect reports: socket_connect(): unable to connect [2]: No such file or directory

Accepting one connection at a time

As the hardware I was working with prohibits parallelism, only one request should be handled by it at a time. It was tempting for me to leave the socket open and push the requests into a queue, but how’d the C++ program know if one request is still valid without the PHP program first look at the respond of the last request?

As a result, I simply closed the socket once a connection is established and something is being sent in. The program handles the requets, send a response back and then binds to the socket again.

To do so, we define a function bind_listen_socket that wraps all the code needed to create, bind and listen to a socket. This function will be called when the program starts and after a request has been processed. This piece of code was originally in main but now it’s being moved to a function.

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <signal.h>

using namespace std;

#define SOCKET_FILENAME "/tmp/server.sock"

int server;

void bind_listen_socket(int &server, sockaddr_un &server_addr)
{
  // create socket
  server = socket(PF_UNIX, SOCK_STREAM, 0);
  if (!server) {
    perror("socket");
    exit(-1);
  }

  // call bind to associate the socket with our local address and
  // port
  if (bind(server, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("bind");
    exit(-1);
  }

  // convert the socket to listen for incoming connections
  if (listen(server, 0) < 0) {
    perror("listen");
    exit(-1);
  }

  puts("Listening to connection...");

}

The rest of the program remains similar to the orginal version, however it calls bind_listen_socket whenever it wants to create, bind and listen to a socket. Moreover, the socket file descriptor is closed and the socket file removed once there is a request coming in from a client.

void signal_callback_handler(int signum)
{
  // close server
  close(server);
  // remove the socket file
  unlink(SOCKET_FILENAME);
  // signal handled
  exit(0);
}

int main(int argc, char **argv)
{
  struct sockaddr_un server_addr, client_addr;
  socklen_t clientlen = sizeof(client_addr);
  int client, buflen, nread;
  char *buf;

  puts("Hell World");

  // listen to SIGINT, SIGTERM, and SIGKILL
  signal(SIGINT, signal_callback_handler);
  signal(SIGTERM, signal_callback_handler);
  signal(SIGKILL, signal_callback_handler);

  // setup socket address structure
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sun_family = AF_UNIX;
  strcpy(server_addr.sun_path, SOCKET_FILENAME);

  // bind and listen on the socket file
  bind_listen_socket(server, server_addr);

  // allocate buffer
  buflen = 1024;
  buf = new char[buflen+1];

  // loop to handle all requests
  while (1) {
    unsigned int client = accept(server, (struct sockaddr *)&client_addr, &clientlen);

    // got a request, close the socket
    close(server);
    unlink(SOCKET_FILENAME);

    // read a request
    memset(buf, 0, buflen);
    nread = recv(client, buf, buflen, 0);

    printf("\nClient says: %s\n\n", buf);

    // echo back to the client
    send(client, buf, nread, 0);

    close(client);

    sleep(2);

    // re-bind and listen on the socket
    bind_listen_socket(server, server_addr);
  }

  close(server);

  unlink(SOCKET_FILENAME);

  return 0;
}

During the execution of the while loop body and that 2 seconds delay I added to demonstrate that the socket really does not take any new connection during the execution. This is illustrated below. When trying to connect to the socket, the client couldn’t open the socket file during the 2-second period.

/run/app # php client.php Hi
Socket createdConnection established 
Message send successfully 
Message received 
string(2) "Hi"

/run/app # php client.php Hi
Socket created
Warning: socket_connect(): unable to connect [2]: No such file or directory in /run/app/client.php on line 14
Could not connect: [2] No such file or directory 

/run/app # php client.php Hi
Socket createdConnection established 
Message send successfully 
Message received 
string(2) "Hi"

Remarks

During the handling of my job and creation of this post, I learned that socket in UNIX domain is a very fast and effective way to achieve interprocess communication using different programming languages

The code used as examples here are available at tommyku/cpp-php-socket-demo. As I was searching for socket programming with PHP and C++, a tutorial from BinaryTides and zappala/socket-programming-examples-c have been very useful.

You could also look at...