What is Inter Process Communication (IPC)?
Inter process communication refers to passing of information between processes. These processes can be located either on a single host or on multiple hosts in a network. According to the context in which they operate techniques used for inter process communication should differ to obtain the maximum performance. Given below are some of the popular inter process communication methods.
Different techniques of IPC
Not all operating systems support all IPC techniques. The IPC techniques mentioned below also have different speeds of execution, convenience, reliability levels and security levels. It’s interesting and important to have a good understanding about other alternatives before analyzing one approach of inter process communication in detail. Some of the widely used IPC techniques and the OS support they have are mentioned below,
-
Files: Supported by all Operating Systems. Very primitive mechanism. Not efficient and highly vulnerable.
-
Message Passing: Used in Java RMI, CORBA, etc. Has lot of limitations.
-
Shared Memory: Supported by all POSIX Systems and Windows Systems. Faster than both Pipes and Sockets.
-
Sockets: Supported by all Operating Systems. Efficient in server client communication in a network environment.
-
Pipes and Named Pipes: Supported by all POSIX Systems and Windows Systems. Slightly faster than Sockets.
Pipes as an Inter Process Communication Technique
Pipe communication is a effective server/client communication mechanism which enables communication between a server and 1 or more clients. Pipes has 2 forms which has different characteristics. They are,
-
"Anonymous pipes" (or "Unnamed pipes")
-
"Named Pipes".
Let’s look at both forms separately. But main focus of this article is to introduce you Named Pipes in detail.
Anonymous Pipes
Anonymous Pipes are used to transfer information between related processes. It should be noted that Anonymous pipes cannot be used to communicate among processes over a network. Unnamed pipes are also unidirectional by nature. Hence to enable bidirectional communication between server and a client, 2 separate pipes need to be created. Normally to satisfy server/client communication between 2 processes, 2 dedicated pipes are created one for reading and one for writing in this scenario. The lifespan of the unnamed pipe is tightly bounds with the lifespan of the process that creates the pipe.
Usage: This is an efficient mechanism to redirect standard inputs or outputs of a child process with its parent process within the same host.
Named Pipes
Named Pipes in the other hand are used to exchange information between related or unrelated processes, within a single host or within multiple hosts over a network. Name of the pipe is assumed to be well known. And any process can access the pipe if it knows the name and has the right privileges. Lifespan of a named pipe is not tightly bound to the lifespan of the process that creates the pipe and hence needed to be explicitly closed at the end of communication. Most important fact about named pipes is that not like anonymous pipes, the same named pipe can act as a server and a client at different occasions. Hence all named pipe peers are equal.
Named Pipe Example Code
Since pipes behave similar to files, any mechanism used to create or access files can be used for creating or accessing pipes. But we need to keep in mind that the inputs and outputs of a pipe are always byte streams.
There are 4 important methods to implement when using Named pipes for IPC. They are,
-
StartClient
-
ReadPipe
-
WritePipe
-
StopClient
Approach #1 will demonstrate how we can implement a Named Pipe Client which has all these methods and uses 2 separate pipes to establish communication.
public class NamedPipeClient {
private static InputStream pipeIn;
private static OutputStream pipeOut;
public NamedPipeClient() {
public void StartClient()
}
public void StopClient() {
pipeIn = null;
pipeOut = null;
}
public void StartClient() {
//initialize the pipe after a small sleep time
File pipe = new File(“\\\\.\\pipe\\testpipe”);
try{
pipeOut = new FileOutputStream(pipe);
pipeIn = new FileInputStream(pipe);
}catch(FileNotFoundException e){
e.printStackTrace();
}
}
public boolean Write(byte[] m_pTxDataBuffer, short totalpktLength) {
boolean wSuccess = false;
try {
pipeOut.write(m_pTxDataBuffer, 0, totalpktLength);
pipeOut.flush();
wSuccess = true;
} catch (NullPointerException e) {
System.out.println("Exception! Write buffer is null.");
e.printStackTrace();
}catch(IndexOutOfBoundsException e){
System.out.println("Exception! Write length exceeds write buffer size or total packets to write contain a invalid value.");
e.printStackTrace();
}catch (IOException e){
System.out.println("IOException while writing to the pipe.");
e.printStackTrace();
}
return wSuccess;
}
public int Read(byte[] pchBuff, int inBufferSize) {
int byteTransfer = 0;
byteTransfer = pipeIn.read(pchBuff, 0, inBufferSize);
} catch (NullPointerException e) {
System.out.println("Exception! Read buffer is null.");
e.printStackTrace();
}catch (IndexOutOfBoundsException e) {
System.out.println("Exception! Read length exceeds read buffer size.");
e.printStackTrace();
}catch (IOException e) {
System.out.println("IO Exception while reading from the pipe.");
e.printStackTrace();
}
return byteTransfer;
}
}
Approach #2 will demonstrate how we can implement a simple Named Pipe Client which has all these methods and uses a single bidirectional pipe to establish communication.
public class NamedPipeClient {
RandomAccessFile rafPipe = null;
public NamedPipeClient() {
StartClient();
}
public void StopClient() {
try {
rafPipe.close();
} catch (IOException e) {
System.out.println("IOException while closing the pipe streams");
e.printStackTrace();
}
rafPipe = null;
}
public void StartClient() {
//create the pipe
try {
rafPipe = new RandomAccessFile( pipeName, "rws");
} catch (FileNotFoundException e) {
System.out.println("FileNotFound exception while creating the pipe...");
e.printStackTrace();
}
}
public boolean write(byte[] m_pTxDataBuffer, short totalpktLength) {
boolean wSuccess = false;
try {
rafPipe.write(m_pTxDataBuffer, 0, totalpktLength);
wSuccess = true;
}catch (IOException e){
System.out.println("IOException while writing to the pipe...");
e.printStackTrace();
}
return wSuccess;
}
public int Read(byte[] pchBuff, int inBufferSize) {
int byteTransfer = 0;
try {
byteTransfer = rafPipe.read(pchBuff, 0, inBufferSize);
}catch (IOException e) {
System.out.println("IO Exception while reading from the pipe.");
e.printStackTrace();
}
return byteTransfer;
}
}
Approach #3 will demonstrate how we can improve the Named Pipe Client mentioned in Approach #2 to support non-blocking reads. If explained further, in above example the read function get blocked until at least one byte is read from the pipe. Hence when reading from the pipe is performed continuously from a separate thread, even writes can get blocked when there is nothing to read. For this I have used the length() method available with RandomAccessFile which will provide the pipe data length in a non blocking way. Since StopClient() and StartClient() methods are identical to what is given in Approach #2, I’ll not include them here. In adding to introducing non-blocking read functionality I have introduced locking scheme to prevent deadlocks.
public class NamedPipeClient {
RandomAccessFile rafPipe = null;
static long curLength;
static boolean writeNotify = false;
static boolean wrote = false;
static boolean readNotify = false;
public boolean write(byte[] m_pTxDataBuffer, short totalpktLength) {
boolean wSuccess = false;
if(totalpktLength<0){
totalpktLength = 0;
}
try {
while(readNotify){}//wait for read to finished
writeNotify = true;
rafPipe.write(m_pTxDataBuffer, 0, totalpktLength);
wrote = true;
}catch (IOException e){
System.out.println("IOException while writing to the pipe...");
e.printStackTrace();
}finally{
writeNotify = false;
}
return wSuccess;
}
public int Read(byte[] pchBuff, int inBufferSize){
int byteTransfer = -1;
if(inBufferSize<0){
inBufferSize = 0;
}
try{
curLength = rafPipe.length();
}catch(IOException e){
curLength = -1;
}
//handle exceptions
if(curLength == -1){
return byteTransfer;
}
//handle functionality
if(!wrote && curLength==0){
//nothing happened
return byteTransfer;
}else if(!wrote && curLength>0){
//incoming stream. read now
try{
while(writeNotify){}//wait for write to finish
readNotify = true;
byteTransfer = rafPipe.read(pchBuff, 0, inBufferSize);
}catch (IOException e) {
System.out.println("IO Exception while reading from the pipe.");
e.printStackTrace();
}finally{
readNotify = false;
}
}else if(wrote && curLength==0){
//read possible. But not yet available. wait till timeout.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return byteTransfer;
}else if(wrote && curLength>0){
//input stream available. read now
try{
while(writeNotify){}//wait for write to finish
readNotify = true;
byteTransfer = rafPipe.read(pchBuff, 0, inBufferSize);
}catch (IOException e) {
System.out.println("IO Exception while reading from the pipe.");
e.printStackTrace();
}finally{
wrote = false;
readNotify = false;
}
}else{
//unknown condition
}
return byteTransfer;
}
}
Hope this article helped you to get a better idea on Named Pipes which will help you in your future work..