Thursday, 29 January 2015

What is Unix or Domain Socket? How to implement unix Socket in C?

In this post, i will explain about Unix/Domain Sockets.

Why Unix Sockets are Required?


You might have heard different types of sockets like TCP, UDP, SCTP, RAW etc.
These sockets are useful best for inter communications where IP address and port number (TCP /UDP Header) is most important.

When there are two process let's say X and Y reside in the same system(machine) needs to communicate each other, then which sockets would be the better option?

For the above case, i would prefer to use UNIX sockets rather than TCP/UDP sockets, since there is no over head of TCP/UDP header.  Unix Sockets doesn't carry any TCP/UDP header for communication.

What is Unix Socket?

A Unix domain socket  is a data communications endpoint for exchanging data between processes executing within the same host operating system. 

While similar in functionality to named pipes.Unix domain sockets may be created as connection‑mode (SOCK_STREAM or SOCK_SEQPACKET) or as connectionless (SOCK_DGRAM), while pipes are streams only. 

Processes using Unix domain sockets do not need to share a common ancestry. The API for Unix domain sockets is similar to that of an Internet socket, but it does not use an underlying network protocol for communication. 

Unix domain sockets use the file system as their address name space. They are referenced by processes as inodes in the file system. This allows two processes to open the same socket in order to communicate. However, communication occurs entirely within the operating system kernel.

In addition to sending data, processes may send file descriptors across a Unix domain socket connection using the sendmsg() and recvmsg() system calls. 

How to Implement Unix Sockets?

In Unix sockets,  the socket API are similar to internet socket.

Unix Socket create using API:

 if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        perror("server: socket");
        return -1;
    }


    /*
     * Create the address we will be connecting to.
     */
    address.sun_family = AF_UNIX;
    snprintf(address.sun_path, UNIX_PATH_MAX, "demo_socket");


UNIX CLIENT PROGRAM
-----------------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>

#define UNIX_PATH_MAX 108

/*
 * Strings we send to the server.
 */
char str[100] ;

int main()
{
    int sock_fd= -1, i, len;
 
    /* Unix Sock Address structure */
    struct sockaddr_un address;

    /*
     * Create domain socket.
     * This socket will be in the UNIX domain,
     * and will be a stream socket.
     */
    if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        perror("server: socket");
        return -1;
    }
     /* start with a clean address structure */
    memset(&address, 0, sizeof(struct sockaddr_un));

    /*
     * Create the address we will be connecting to.
     */
    address.sun_family = AF_UNIX;
    snprintf(address.sun_path, UNIX_PATH_MAX, "demo_socket");



    /*
     * Connect to the address.  For this to
     * succeed, the server must already have bound
     * this address, and must have issued a listen()
     * request.
     *
     * The third argument indicates the "length" of
     * the structure.
     *
     */
    if(connect(sock_fd,
            (struct sockaddr *) &address,
            sizeof(struct sockaddr_un)) != 0)
     {
       printf("connect() failed\n");
       return 1;
     }

    /* Send and recieve Data
     *   in while Loop
     */
    while(printf("> "), fgets(str, 100, stdin), !feof(stdin))
    {
        /* Send the Data to server */
        if (send(sock_fd, str, strlen(str), 0) == -1)
         {
            perror("send");
            return 1;
         }

        /* Recieve the Date from Server */
        if ((len=recv(sock_fd, str, 100, 0)) > 0)
         {
            str[len] = '\0';
            printf("echo> %s", str);

         }
        else
         {
            if (len < 0)
              perror("recv");
            else // Length = 0
              printf("Server closed connection\n");
              return 1;
        }
     }
    /*
     * We can simply use close() to terminate the
     * connection, since we're done with both sides.
     */
    close(sock_fd);

    return 0;
}

UNIX SERVER PROGRAM
-----------------------------------

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>

#define UNIX_PATH_MAX  108

char str[100];

int main()
{

    /* Sock Fd and Connection Fd = -1 */
    int  sock_fd= -1, connection_fd = -1, listen_fd =-1, i;

   /* unix Socket address Structure */
    struct sockaddr_un address;
   
    /* Socket Address Len */
    socklen_t address_len;

    /*
     * Get a socket to work with.  This socket will
     * be in the UNIX domain, and will be a
     * stream socket.
     */
    if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        perror("server: socket");
        return -1;
    }

     /* start with a clean address structure */
     memset(&address, 0, sizeof(struct sockaddr_un));

    /*
     * Create the address we will be binding to.
     */
     address.sun_family = AF_UNIX;
     snprintf(address.sun_path, UNIX_PATH_MAX, "demo_socket");


    /*
     * Bind the address to the socket.  We
     * unlink the name first so that the bind won't
     * fail.
     *
     * The third argument indicates the "length" of
     * the structure, not just the length of the
     * socket name.
     */

     unlink("demo_socket");
    
     if(bind(sock_fd,
                 (struct sockaddr *) &address,
                 sizeof(struct sockaddr_un)) != 0)
      {
          printf("bind() failed\n");
          return 1;
      }

    /*
     * Listen on the socket.
     */
      if( listen_fd = listen(sock_fd, 5) != 0)
       {
          printf("listen() failed\n");
          return 1;
       }
    
    /*
     * Accept connections.  When we accept one, ns
     * will be connected to the client.  fsaun will
     * contain the address of the client.
     */
    while (1)
    {
       int done, len;

       connection_fd = accept(sock_fd,
             (struct sockaddr *)&address,
             &address_len);
       if(connection_fd < 0)
       {
         perror("server: accept");
         return -1;
       }
      
       close(listen_fd);
      
       /* Recv and Send in Infinite Loop */
       done = 0;
       do
       {
          /* Recieve Data from Client */
          len = recv(connection_fd, str, 100, 0);

          if (len <= 0)
          {
             if (len < 0)
                perror("recv");
             done = 1;
          }

          if (!done)
             /* Send Data to Client */
             if (send(connection_fd, str, len, 0) < 0)
              {
                  perror("send");
                  done = 1;
              }
        } while (!done);

      /*
       * We can simply use close() to terminate the
       * connection, since we're done with both sides.
       */
   
      close(connection_fd);
    }
    return 0;
}


--------------------------------------------------------------
 HOW UNIX SOCKETS ARE USED IN REAL TIME APPLICATIONS
------------------------------------------------------------- 
Explain With simple example:

Let's take two process x_process (as a client) and y_process(as a server.
x_process have different call back functions and it shall be triggered based on the message type send from the server(y_process).


x_process registers the call back functions for different Message types during initialization time.

Message Types : HELLO_MESSAGE, DOWN_MESSAGE, UPDATE_MESSAGE.

Based on the respective message, the corresponding call back function is called.

x_process   (CLIENT)
------------
#include<stdio.h>
#include<stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include "message.h"

#define SOCK_PATH "echo_socket"


/* Hello Message Handle Function */
int handle_hello_message_func(int message_type)
{
   printf(" I am in Hello function \n");
   return 0;
}

/* Down Message Handle Function */
int handle_down_message_func(int message_type)
{
   printf(" I am in down function \n");
   return 0;
}

/* Update Message Handle Function */
int handle_update_message_func(int message_type)
{
   printf(" I am in update function \n");
   return 0;
}

/*
 * Trigger Callback function
 */

void trigger_callback_function(char *str, struct handle_msg *msg)
{

  printf("%s", str);

  if(strcmp(str, "HELLO_MESSAGE\n") == 0)
      msg->callback_function[HELLO_MESSAGE](HELLO_MESSAGE);
  else if (strcmp(str, "UPDATE_MESSAGE\n") == 0)
      msg->callback_function[UPDATE_MESSAGE](UPDATE_MESSAGE);
  else if (strcmp(str, "DOWN_MESSAGE\n") == 0)
      msg->callback_function[DOWN_MESSAGE](DOWN_MESSAGE);
  else
      printf(" Message is not Known, Ignored ");
}

/*
 * Unix Socket Creation
 *
 */ 

int unix_socket_init(struct handle_msg **msg)
{
   int len, recv_len, t,s;
 
   /* Declare the remote (Server) structure */
   struct sockaddr_un remote;

   char str[100];

   /* Create Unix Socket */
   (*msg)->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);

   if((*msg)->sock_fd < 0)
    {
      perror("socket");
      return 1;
    }
  
   printf("Trying to connect...\n");

   /* Set the server Attributes */
   remote.sun_family = AF_UNIX;
   strcpy(remote.sun_path, SOCK_PATH);
   
   /* Calculate the length */
   len = strlen(remote.sun_path) + sizeof(remote.sun_family);
  
   /* Connect to the server */
   if (connect((*msg)->sock_fd, (struct sockaddr *)&remote, len) == -1) {
        perror("connect");
        return 1;
    }

   printf("Connected.\n");

   s= (*msg)->sock_fd;

   /* Send and recieve in while Loop */
   while(printf("> "), fgets(str, 100, stdin), !feof(stdin)) {
        if (send(s, str, strlen(str), 0) == -1) {
            perror("send");
            exit(1);
        }

        if ((t=recv(s, str, 100, 0)) > 0) {
            str[t] = '\0';
            printf("echo> %s", str);

        /* Trigger the Respective Call back Function */
        trigger_callback_function(str, *msg); 

         
        } else {
            if (t < 0) perror("recv");
            else printf("Server closed connection\n");
            exit(1);
        }
     }
   close(s);
}

/* Intialise the X process
 * Step 1 : Create Unix Socket
 * Step 2 : Register the CallBack functions with the Lib Code
 *
 *
 */
void x_client_process_init()
{

   struct handle_msg *msg= NULL;

   /* Allocate Message Structure */
   msg= (struct handle_msg *)malloc(sizeof(struct handle_msg));


   /* Register the server HELLO call back function */
   client_register_callback_function(msg, HELLO_MESSAGE,
                                     handle_hello_message_func );

   /* Register the server DOWN call back function */
   client_register_callback_function(msg, DOWN_MESSAGE,
                     handle_down_message_func);

   /* Register the server DOWN call back function */
   client_register_callback_function(msg, UPDATE_MESSAGE,
                     handle_update_message_func);

 
   /* Create Unix Socket and  connect to the server*/
   unix_socket_init(&msg);
}

int main()
{
   /* Intialise the X Client Process */
   x_client_process_init();
  
 
   return 0;
}

message.h  (Common File)
========================

/* DECLARE THE DIFFERENT Message TYPES between Server and Client */
#define HELLO_MESSAGE                   0
#define UPDATE_MESSAGE                  1
#define DOWN_MESSAGE                    2
#define MAX_MESSAGE                     3

/* There are many ways to declare array of Call back functions */
/* One way is :
 *  -> int (*fun[10])(int, int) or
 *  -> typedef int (*func)(int, int); func arrayfunc[10]
 */
/* Declare the Message Call back handler function */
typedef int (*CALLBACK_MESSAGE)(int message_type);




/* Common Message Structure for Client and Server */
struct handle_msg
{

  /* Socket to accept or connect.  */
  int sock_fd;

  /* functions registers in to this call back function */
  CALLBACK_MESSAGE callback_function[MAX_MESSAGE];
};

/* Register the call back functions
 * once the functions are registered and will be
 * triggered dynamically
 *
 */

void client_register_callback_function(struct handle_msg *msg,
                              int message_type,
                              CALLBACK_MESSAGE cb)
{
  if (message_type >= MAX_MESSAGE)
    return;

  msg->callback_function[message_type] = cb;
}


Y_PROCESS (Server)
================
#include<stdio.h>
#include<stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include "message.h"

#define SOCK_PATH "echo_socket"

/*
 * Server Unix Socket Creation
 *
 */
int server_unix_socket_init(struct handle_msg **msg)
{

   int s, connect_fd, t, len;
   struct sockaddr_un local, remote;
   char str[100];


   /* Create Unix Socket */
   (*msg)->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);

   if((*msg)->sock_fd < 0)
    {
       perror("socket");
       return 1;
    }

   /* Populate the local structure */
   local.sun_family = AF_UNIX;
   strcpy(local.sun_path, SOCK_PATH);
  
   /* Unlink the path */
   unlink(local.sun_path);
  
   len = strlen(local.sun_path) + sizeof(local.sun_family);
  
   /*
    * Bind to the Local path -> For Unix Sockets there will not
    * be any port Num or IP address
    *
    * It will bind to the local path.
    */   

   if (bind((*msg)->sock_fd, (struct sockaddr *)&local, len) == -1)
    {
        perror("bind");
        return 1;
    }

    if (listen((*msg)->sock_fd, 5) == -1) {
        perror("listen");
        return 1;
    }

    for(;;)
      {
        int done, n;
        printf("Waiting for a connection...\n");
        len = sizeof(remote);
       
  
        connect_fd = accept((*msg)->sock_fd, (struct sockaddr *)&remote, &len);
       
        if((*msg)->sock_fd < 0)
         {
            perror("accept");
            return 1;
         }

        printf("Connected.\n");

 
        done = 0;
        do {
            n = recv(connect_fd, str, 100, 0);
            if (n <= 0) {
                if (n < 0) perror("recv");
                done = 1;
            }

            if (!done)
                if (send(connect_fd, str, n, 0) < 0) {
                    perror("send");
                    done = 1;
                }
        } while (!done);
         
        /* Send and recieve in while Loop */
        close(connect_fd);
     }
}
/*
 * Y server process init()
 */
void y_server_process_init()
{
   struct handle_msg *msg= NULL;

   /* Allocate Message Structure */
   msg= (struct handle_msg *)malloc(sizeof(struct handle_msg));


   /* Create Server Unix Socket, listen and accept the connection*/
   server_unix_socket_init(&msg);

}
/*
 * Main()
 */
int main()
{
   /* Intialise the Y Process */
   y_server_process_init();

}



NOTE: Give Input as " HELLO_MESSAGE\n"  or DOWN_MESSAGE\n, UPDATE_MESSAGE\n to get proper output.   "\n" is getting added to the string while reading.









2 comments: