Inter-process Communication (IPC)

Contents

Instructions

game

engine.c

Downloads

Licence

Source Files

Shell script to C program inter-process communication (IPC) using named pipes (FIFO).

Instructions

Start the game script with ./game, which in turn starts and backgrounds the engine. The game script then enters an event processing loop which waits when reading from the named pipe PIPEOUT and the engine enters an event generating / function processing loop that doesn't wait when reading from PIPEIN. The significance of this is that the engine could be an event based engine using a library such as SDL with the game's logic being written using bash in the game script. Without taking this any further I wouldn't know what the speed and timing of the communication would be like, but I'm sure it would be efficient for creating board, card and puzzle type games.

Pressing CTRL+C will quit the game script, which then tells the engine to shut down. Alternatively in another terminal you can simply echo 2 to the PIPEIN (e.g. echo 2 > /home/user/.basheng/pipein_3869_23684 although the PIPEIN name is randomnly generated but will be displayed in the terminal) with 2 being the code for FUNC_QUIT which the engine receives and subsequently sends an EVENT_QUIT to the game script resulting in both shutting down.

The first time you run game you will see a series of messages in a similar order to mine below, although running it again will probably mean that you won't see the message "Waiting briefly for both pipes to become available...", even though the pipes are new each time.

user[basheng]$ ./game
./game: engine pid=3871
./game: Waiting briefly for both pipes to become available...
./engine: PIPEIN to be named /home/user/.basheng/pipein_3869_23684
./engine: PIPEOUT to be named /home/user/.basheng/pipeout_3869_24762
./engine: Opening PIPEIN for reading
./game: Sending FUNC_INIT
./engine: PIPEIN is now open
./engine: Opening PIPEOUT for writing
./game: Entering event processing loop
./engine: PIPEOUT is now open
./engine: Entering function processing loop
./engine: Received FUNC_INIT
./engine: Sending EVENT_INIT
./game: Received EVENT_INIT
./game: Sending FUNC_DO_SOMETHING 123 456
./engine: Received FUNC_DO_SOMETHING 123 456
./engine: Replying to FUNC_DO_SOMETHING
./game: FUNC_DO_SOMETHING returned 579
*** CTRL+C pressed here ***
./engine: Received FUNC_EXIT
./engine: Closing PIPEIN
./engine: Closing PIPEOUT
./engine: Removing PIPEIN
./engine: Removing PIPEOUT
./engine: Goodbye
user[basheng]$

game

#!/bin/bash

# game
# Copyright (C) 2009-2013 Thunor <thunorsif@hotmail.com>
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Object IDs
OBJECT_ROOT=1

# Event IDs
EVENT_INIT=1
EVENT_QUIT=2

# Function IDs
FUNC_INIT=1
FUNC_QUIT=2
FUNC_EXIT=3
FUNC_DO_SOMETHING=4

# Make settings folder if it doesn't already exist
mkdir -p $HOME/.basheng

# Pipe names
PIPEIN=$HOME/.basheng/pipein_$$_$RANDOM
PIPEOUT=$HOME/.basheng/pipeout_$$_$RANDOM

# Start the engine and background it
./engine $PIPEIN $PIPEOUT &
echo "$0: engine pid=$!"

# Force engine shutdown on script exit e.g. ctrl+c
trap "if [ -p $PIPEIN ]; then echo $FUNC_EXIT > $PIPEIN; fi" EXIT

# If necessary, wait a few seconds for both pipes to become available.
# On my PC, most of the time it's instant, but on a hendheld device I
# have experienced a 3 second delay.
if [ ! -p $PIPEIN -o ! -p $PIPEOUT ]; then
	echo "$0: Waiting briefly for both pipes to become available..."
	STARTTIME=$(date "+%s"); ENDTIME=STARTTIME; MAXTIME=9
	until [ $(($ENDTIME-$STARTTIME)) -gt $MAXTIME ]; do
		ENDTIME=$(date "+%s")
		if [ -p $PIPEIN -a -p $PIPEOUT ]; then break; fi
	done
fi

if [ -p $PIPEIN -a -p $PIPEOUT ]; then
	# Sending FUNC_INIT results in the engine unblocking on 
	# "pipein = open(pipein_filename, O_RDONLY);" and eventually
	# sending EVENT_INIT which we'll use to set-up the game/engine
	echo "$0: Sending FUNC_INIT"
	echo $FUNC_INIT > $PIPEIN

	echo "$0: Entering event processing loop"
	while true
	do
		# read will wait until something is received
		if read OBJECT EVENT VALUE1 VALUE2 VALUE3 < $PIPEOUT; then
			if [ $OBJECT == $OBJECT_ROOT ]; then
				if [ $EVENT == $EVENT_INIT ]; then
					echo "$0: Received EVENT_INIT"
					PARAM1=123; PARAM2=456
					echo "$0: Sending FUNC_DO_SOMETHING $PARAM1 $PARAM2"
					# This will result in a reply
					echo $FUNC_DO_SOMETHING $PARAM1 $PARAM2 > $PIPEIN
					read RETVAL < $PIPEOUT
					echo "$0: FUNC_DO_SOMETHING returned $RETVAL"
				elif [ $EVENT == $EVENT_QUIT ]; then
					echo "$0: Received EVENT_QUIT"
					# Shutdown the game
					echo "$0: Sending FUNC_EXIT"
					# This will not result in a reply
					echo $FUNC_EXIT > $PIPEIN
					break
				fi
			fi
		fi
	done

	echo "$0: Goodbye"
	exit 0
else
	echo "$0: Aborting due to unavailable pipes"
	exit 1
fi

engine.c

/* engine.c
 * Copyright (C) 2009-2013 Thunor <thunorsif@hotmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>	/* For mkfifo */
#include <errno.h>		/* For errno */
#include <fcntl.h>		/* For open, O_RDONLY and O_WRONLY */
#include <unistd.h>		/* For read and write */

/* Object IDs */
#define OBJECT_ROOT 1

/* Event IDs */
#define EVENT_INIT 1
#define EVENT_QUIT 2

/* Function IDs */
#define FUNC_INIT 1
#define FUNC_QUIT 2
#define FUNC_EXIT 3
#define FUNC_DO_SOMETHING 4

int main(int argc, char *argv[]) {
	char pipeout_filename[255], pipein_filename[255], pipebuffer[255];
	int retval, pipeoutfd, pipeinfd, loopcount, func_id, intparam[3];
	char error_message[256];

	/* Check command line */
	if (argc < 3) {
		printf("%s: PIPEIN or PIPEOUT missing from command line\n", argv[0]);
		exit (1);        
	}

	/* Store the pipe names passed via the command line */
	strcpy(pipein_filename, argv[1]);
	printf("%s: PIPEIN to be named %s\n", argv[0], argv[1]);
	strcpy(pipeout_filename, argv[2]);
	printf("%s: PIPEOUT to be named %s\n", argv[0], argv[2]);

	/* Create the pipe PIPEIN */
	retval = mkfifo(pipein_filename, 0666);
	if ((retval == -1) && (errno != EEXIST)) {
		sprintf(error_message, "%s: Error creating PIPEIN %s", argv[0], pipein_filename);
		perror(error_message);
		exit (1);
	}

	/* Create the pipe PIPEOUT */
	retval = mkfifo(pipeout_filename, 0666);
	if ((retval == -1) && (errno != EEXIST)) {
		sprintf(error_message, "%s: Error creating PIPEOUT %s", argv[0], pipeout_filename);
		perror(error_message);
		exit (1);
	}

	/* Open PIPEIN for reading (very important this is done first) */
	printf("%s: Opening PIPEIN for reading\n", argv[0]);
	pipeinfd = open(pipein_filename, O_RDONLY);
	printf("%s: PIPEIN is now open\n", argv[0]);

	/* Open PIPEOUT for writing (very important this is done second) */
	printf("%s: Opening PIPEOUT for writing\n", argv[0]);
	pipeoutfd = open(pipeout_filename, O_WRONLY);
	printf("%s: PIPEOUT is now open\n", argv[0]);

	printf("%s: Entering function processing loop\n", argv[0]);
	while (1) {
		/* This will proceed if nothing is read */
		retval = read(pipeinfd, pipebuffer, 255);
		/* printf("%s: Loop %i\n", argv[0], loopcount++); */	/* Uncomment this to see it looping */
		if (retval > 0) {
			pipebuffer[retval] = 0;	/* Must terminate string */
			func_id = 0;
			retval = sscanf(pipebuffer, "%i", &func_id);
			if (retval > 0) {
				if (func_id == FUNC_INIT) {
					printf("%s: Received FUNC_INIT\n", argv[0]);
					/* Send an init event to enable the game to initialise */
					sprintf(pipebuffer, "%i %i\n", OBJECT_ROOT, EVENT_INIT);	/* \n is very important */
					printf("%s: Sending EVENT_INIT\n", argv[0]);
					write(pipeoutfd, pipebuffer, strlen(pipebuffer));
				} else if (func_id == FUNC_QUIT) {
					printf("%s: Received FUNC_QUIT\n", argv[0]);
					/* Send a quit event to enable the game to do some tidying up */
					sprintf(pipebuffer, "%i %i\n", OBJECT_ROOT, EVENT_QUIT);	/* \n is very important */
					printf("%s: Sending EVENT_QUIT\n", argv[0]);
					write(pipeoutfd, pipebuffer, strlen(pipebuffer));
				} else if (func_id == FUNC_EXIT) {
					printf("%s: Received FUNC_EXIT\n", argv[0]);
					/* Shutdown the engine */
					break;
				} else if (func_id == FUNC_DO_SOMETHING) {
					intparam[0] = 0; intparam[1] = 0;
					sscanf(pipebuffer, "%i %i %i", &func_id, &intparam[0], &intparam[1]);
					printf("%s: Received FUNC_DO_SOMETHING %i %i\n", argv[0], intparam[0], intparam[1]);
					/* Send something back */
					sprintf(pipebuffer, "%i\n", intparam[0] + intparam[1]);		/* \n is very important */
					printf("%s: Replying to FUNC_DO_SOMETHING\n", argv[0]);
					write(pipeoutfd, pipebuffer, strlen(pipebuffer));
				} else {
					printf("%s: Received unknown func_id=%i\n", argv[0], func_id);
				}
			}
		}
	}

	/* Close PIPEIN */
	printf("%s: Closing PIPEIN\n", argv[0]);
	close(pipeinfd);

	/* Close PIPEOUT */
	printf("%s: Closing PIPEOUT\n", argv[0]);
	close(pipeoutfd);

	/* Remove PIPEIN */
	printf("%s: Removing PIPEIN\n", argv[0]);
	unlink(pipein_filename);

	/* Remove PIPEOUT */
	printf("%s: Removing PIPEOUT\n", argv[0]);
	unlink(pipeout_filename);

	printf("%s: Goodbye\n", argv[0]);

	return 0;
}

Downloads

Licence

GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007

Source Files