Let’s discuss how to pass data from a C program to a Python Script and vice-versa, how to pass data between a Python Script and C program. As with most things in software; there are a LOT of ways to do this. In this blog post we’ll discuss the more exotic Unix domain datagram socket. Specifically, this is domain AF_UNIX and type SOCK_DGRAM type of socket. Before you consider this method let’s address some of it’s benefits/limitations:

  • Must be for local communication, meaning the processes are running on the same host. This is largest drawback I see. Especially, considering a microservice world where programs are typically on different hosts.
  • Datagrams are carried through the kernel and this method is always reliable
  • The sender and receiver of the datagram must agree on the max size of the socket
  • Messaging can be one-way or two-way. Meaning, we can fire-and-forget or effectively acknowledge what was sent.
  • Low overhead, especially in relation to TCP/IP
  • Asynchronous Communication

Why Unix Domain Datagram sockets over using Internet Sockets or FIFOs this is because:

  • Don’t need overhead of TCP/IP
  • FIFOs the have a writer and reader. But the writer is blocked until the reader reads. This blocking isn’t desired, although, there are ways around it O_NONBLOCK

C Socket Server

Let’s start with a simple socket server in C. Here we will open a socket called serv_sock and it will be a file on the filesystem. When the program starts if the socket file is present we will remove it and create it again. When this server receives a datagram it will convert all the bytes to upper case and return them to the client. This server just runs forever waiting for datagrams from a client.

Now for the implementation of the server.

// server.h
#include <sys/un.h>
#include <sys/socket.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdlib.h>

#define BUF_SIZE 10
#define SV_SOCK_PATH "serv_sock"

Now for the implementation of the server.

// server.c
#include "server.h"

int
main(int argc, char *argv[])
{
  struct sockaddr_un svaddr, claddr;
  int sfd, j;
  ssize_t numBytes;
  socklen_t len;
  char buf[BUF_SIZE];

  sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
  if(sfd == -1)
  {
    fprintf(stderr, "error opening socket");
    return 1;
  }

  if(strlen(SV_SOCK_PATH) > sizeof(svaddr.sun_path)-1)
  {
    fprintf(stderr, "socket path too long must be %d chars", sizeof(svaddr.sun_path-1));
    return 2;
  }

  if(remove(SV_SOCK_PATH) == -1 && errno != ENOENT)
  {
    fprintf(stderr, "error removing socket %d", errno);
    return 2;
  }

  memset(&svaddr,0,sizeof(struct sockaddr_un));
  svaddr.sun_family = AF_UNIX;
  strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path)-1);

  if(bind(sfd, (struct sockaddr*) &svaddr, sizeof(struct sockaddr_un)) == -1)
  {
    fprintf(stderr, "error binding to socket");
    return 3;
  }

  for(;;)
  {
    len = sizeof(struct sockaddr_un);
    numBytes = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr*) &claddr, &len);

    if(numBytes == -1)
    {
      fprintf(stderr, "error recvfrom");
      return 4;
    }

    fprintf(stdout, "server received %ld bytes from %s\n", (long) numBytes, claddr.sun_path);

    for(j=0; j<numBytes; j++)
    {
      buf[j] = toupper((unsigned char) buf[j]);
    }

    j = sendto(sfd, buf, numBytes, 0, (struct sockaddr*) &claddr, len);
    if(j != numBytes)
    {
      fprintf(stderr, "error sendto", strerror(errno));
    }
  }
  exit(EXIT_SUCCESS);
}

Python Client

This Python Client will send datagrams to the C Socket Server. It needs the socket file of the server and will open a socket itself as a client. This method allows for many client sockets and a single server socket.

# client.py
import socket
import sys
import os, os.path
import time

ssock_file = "./serv_sock"
csock_file = "./client_sock_py"

if os.path.exists(csock_file):
  os.remove(csock_file)

csock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
csock.bind(csock_file)

for i in range(1,len(sys.argv)):
  csock.sendto(str.encode(sys.argv[i]), ssock_file)

  (bytes, address) = csock.recvfrom(10)
  msg = bytes.decode('utf-8')
  print('address:',address,'received:',msg)

csock.close()

if os.path.exists(csock_file):
  os.remove(csock_file)

Running the C and Python

$ ./server &
[1] 1496
$ python3 client.py hello world
server received 5 bytes from ./client_sock_py
address: serv_sock received: HELLO
server received 5 bytes from ./client_sock_py
address: serv_sock received: WORLD
$

In the snippet above we run the server as a background process. We then run the Python script to send two datagrams of hello and world and see the server convert those to upper case and send them back as HELLO and WORLD.

Example C Client

While creating the Python script, a C script was also developed for testing. For completeness here is the C client socket program:

// client.h
#include <sys/un.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

#define BUF_SIZE 10
#define SV_SOCK_PATH "serv_sock"}

The corresponding C implementation:

// client.c
#include "client.h"

int
main(int argc, char *argv[])
{
  struct sockaddr_un svaddr, claddr;
  int sfd, j;
  size_t msgLen;
  ssize_t numBytes;
  char buf[BUF_SIZE];

  if(argc < 2 || strcmp(argv[1],"--help") == 0)
  {
    fprintf(stderr,"usage");
    return 1;
  }

  sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
  if(sfd == -1)
  {
    fprintf(stderr, "error opening socket");
    return 2;
  }

  memset(&claddr,0,sizeof(struct sockaddr_un));
  claddr.sun_family = AF_UNIX;
  snprintf(claddr.sun_path, sizeof(claddr.sun_path), "client_%ld", (long) getpid());

  if(bind(sfd, (struct sockaddr*) &claddr, sizeof(struct sockaddr_un)) == -1)
  {
    fprintf(stderr, "error binding to socket");
    return 3;
  }

  memset(&svaddr,0,sizeof(struct sockaddr_un));
  svaddr.sun_family = AF_UNIX;
  strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path)-1);

  for(j=1;j<argc;j++)
  {
    msgLen = strlen(argv[j]);
    if(sendto(sfd, argv[j], msgLen, 0, (struct sockaddr*) &svaddr, sizeof(struct sockaddr_un)) != msgLen)
    {
      fprintf(stderr, "error sendto");
      return 4;
    }

    numBytes = recvfrom(sfd, buf, BUF_SIZE, 0, NULL, NULL);
    if(numBytes == -1)
    {
      fprintf(stderr, "error recvfrom");
      return 5;
    }
    printf("Response %d: %*s\n",j, (int) numBytes, buf);
  }

  if(remove(claddr.sun_path) == -1 && errno != ENOENT)
  {
    fprintf(stderr, "error removing socket");
    return 6;
  }

  exit(EXIT_SUCCESS);
}