Message Passing (IPC) with Neutrino

This tutorial demonstrates Interprocess Communication (IPC) under Neutrino. It allows you to send messages from a client Neutrino program to a server Neutrino program. The message is handled as a simple string of characters. The sent string is reversed to form the reply, so you can see that the string has been sent and received properly (a common fault in such matters is mishandling the string terminator).

An important issue here is that both programs (i.e. processes, or threads if you prefer — both the client and the server are single-threaded) become blocked as a normal part of their operation. For more information on blocking, and the various states that a thread can possibly be in, read down from here, especially under the sub-headings “Thread attributes”, and “Thread life cycle”.

To see the demo in action, invoke two terminal screens (two pterms in Photon, or two virtual consoles if you're staying in text mode). Two terminals are best since the messages from the two programs will be confusing if they are all jumbled together. Ensure that you are running the programs from the same directory, since the client needs to find a configuration file that the server creates, and both the client and server expect the file to be in the current working directory. Run the server first, so that it can create a channel, and save its details to the configuration file. Now you can run the client program and send some messages.

When you want to quit, press return without entering any text when invited to enter a send message by the client program. To terminate the server, press CTRL-C.

The Client

This program reads the process and channel IDs for the server from the file pid.txt, and creates a connection to the channel. It invites the user to enter a message (press return without entering anything to quit). Enter any message and press return --- the program will then send this message to the server. It will then wait (in a blocked state) for the server's reply. It prints out the reply message, and loops back to allow the user to enter a new message. On quitting, the program closes its connection to the server's channel.

The key functions here are ConnectAttach(), MsgSend(), and ConnectDetach.

/*          N E U T R I N O    C L I E N T
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/neutrino.h>

#define RECV_BUFLEN 255

   int channel_id;
   int conn_id;
   pid_t process_id;
   FILE *temp_file;
   char send_msg[255];
   int send_length;
   char recv_msg[RECV_BUFLEN];

/* ______________________________________________________________________ */
main( void )

   /* First, read the nto process' ID from a file. */
   temp_file = fopen( "pid.txt", "r" );
   if (temp_file == NULL )
      fprintf( stderr, "nto_clnt: ERROR: Can't open server-info file\n" );
      exit( EXIT_FAILURE );
   fscanf( temp_file, "%d", &process_id );
   fscanf( temp_file, "%d", &channel_id );
   fclose( temp_file );

   conn_id = ConnectAttach(0, process_id, channel_id, 0, 0);
   printf( "Client connecting to process %d, channel %d\n", 
            process_id, channel_id );
   if (conn_id == -1)
      fprintf( stderr, "client: ERROR: Can't connect\n" );
      exit( EXIT_FAILURE );
   do {
      printf("Enter a message to send (empty string quits) :" );
      strcpy( send_msg, "" );
      scanf( "%[^\n]%*c", send_msg );
      printf( "\n" );

      if ( strlen( send_msg ) > 0 )
         /* Add one to the length to include the NULL terminator byte. */
         send_length = strlen( send_msg ) + 1;

         /* Tell the user what's happening */
         printf( "Sending message {%s}\n", send_msg );

         /* This function will block until the message is received.  Ensure that it
          * is not a problem for the app to be blocked, or that the server always
          * replies soon enough to avoid problems, or both. */
         MsgSend( conn_id, send_msg, send_length, recv_msg, RECV_BUFLEN );

         /* Surround the received reply with braces to show that it has been
          * terminated properly. */
         printf( "Reply received = {%s}\n", recv_msg );
      } /* User did enter something */

   } while ( strlen( send_msg ) > 0 ); /* Quit if nothing to send */

   printf( "Disconnecting from channel, and exiting.\n" );
   /* Disconnect, since we are done for this demo. */
   ConnectDetach( conn_id );

   return( EXIT_SUCCESS );

The Server

This program creates a channel and writes its process and channel IDs to a file called pid.txt. It then runs an infinite loop waiting for messages to be sent to it from clients. In a typical real-world situation, the server performs some significant processing related to the received message, and replies with the result of that processing. To mimic this effect (a delayed and dependent reply) here, I've simply used sleep() and formed a reply by reversing the message string.

If this program is run in the background (using '&' on the shell command line), then make sure that this program is killed before running it a second time. Use ps to see if it is running, and check the file pid.txt to confirm the pid number, then kill that pid.

The key functions here are ChannelCreate(), MsgReceive(), MsgReply(), and ChannelDestroy().

/*         N E U T R I N O    S E R V E R

#include <stdio.h>
#include <stdlib.h> /* for EXIT_FAILURE */
#include <errno.h> /* for EOK */
#include <sys/neutrino.h>

#define RECV_BUFLEN 255

main( void )
   int recv_id;
   int channel_id;
   char recv_msg[RECV_BUFLEN];
   char reply_msg[255];
   int reply_length;
   pid_t this_pid;
   FILE *temp_file;
   int recv_i;
   int reply_i;

   this_pid = getpid();

   /* Set up a channel through which clients can connect to me. */
   channel_id = ChannelCreate(0);
   if (channel_id == -1)
      fprintf( stderr, "server: ERROR: Couldn't create a channel\n" );
      return( EXIT_FAILURE );
   printf( "Server process id = %d, channel id = %d\n", this_pid, channel_id );
   /* Store our process and channel IDs for the client to find me. */
   temp_file = fopen( "pid.txt", "w" );
   fprintf( temp_file, "%d\n", this_pid );
   fprintf( temp_file, "%d", channel_id );
   fclose( temp_file );

   printf( "Press CTRL-C to quit.\n" );
   /* Keep running indefinitely. */
   while (1) 
      /* Block until a message is ready for me on the specified channel.
       * Store the ID of the client, so I can reply to it uniquely. */
      recv_id = MsgReceive( channel_id, recv_msg, RECV_BUFLEN, NULL );

      printf( "Received ID = {%d}, message = {%s}\n", recv_id, recv_msg );

      /* Pause long enough to show the Photon client app being blocked. */
      printf( "Processing reveived message, please wait...\n" );

      /* Make a reply by reversing the sent message (don't include the null
       * byte terminator ) */
      reply_i = strlen(recv_msg)-1;
      for ( recv_i = 0; recv_i < strlen(recv_msg); recv_i++ )
         reply_msg[reply_i] = recv_msg[recv_i];
      /* Re-terminate the reversed string */
      reply_msg[ strlen(recv_msg) ] = '\0';

      /* Add one to the message length to include the NULL terminator byte. */
      reply_length = strlen( reply_msg ) + 1;

      /* Surround the messge with curly braces to indicate that the string
       * has been terminated properly. */
      printf( "Replying with message = {%s}\n", reply_msg );
      /* Send a reply to the client specified by the receive ID. */
      MsgReply( recv_id, EOK, reply_msg, reply_length );
   } /* while true */


Home About Me
Copyright © Neil Carter

Content last updated: 2003-11-21