Inter-Process Communication: pipe(), fork() and C++ STL-like streams

Friday, April 22nd, 2011 | Autor:

I’ve run into some troubles lately trying to communicate processes through C++ STL-like streams with fork() and pipe(), so I’ve created an example of how can this be done in a POSIX environment using the GCC’s libstdc++ (GCC >= 3.4.0).

The scenario is: A parent process wants to handle another program’s input and output streams, a process-level wrapper.

So the example is: A parent process forks into another child process. The parent sends the child a message, the child receives it and outputs it to the parent to show the communication works just fine.

The forked process calls the program we want to control, which in this case is the easiest C++ program ever: Just reads a line from the standard input and outputs a message containing the line just read through the standard output. In addition, another message is sent through the standard error stream.

Controlled program

Let’s see the code for the controlled program:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

using namespace std;

int main()
{
  string message;
  getline(cin, message);

  cout << "Program: I've received: "
       << "'" << message  << "'"
       << endl;

  cerr << "Program: This is the error stream!" << endl;

  return (0);
}

As you can see, we use the standard input/output streams cin, cout and cerr.

Main Program

Now let’s see chunks of the main program:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define CHILD_STDIN_READ pipefds_input[0]
#define CHILD_STDIN_WRITE pipefds_input[1]

int pipefds_input[2], pipefds_output[2], pipefds_error[2];

// Create the pipes
// We do this before the fork so both processes will know about
// the same pipe and they can communicate.

pipe_status = pipe(pipefds_input);
if (pipe_status == -1)
  {
    perror("Error creating the pipe");
    exit(EXIT_FAILURE);
  }

With the definitions, you won’t mistake the pipe ends.

We repeat the same process for the pipes for standard output and error.

fork() skeleton


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pid_t pid;
pid = fork();

if (pid == pid_t(0))
  {
    // Child process
  }
else if (pid > pid_t(0))
  {
    // Parent
  }
else
    // Error: fork failed

// Shared code

This is the skeleton for a fork(). When you call fork(), a new process is created in memory and the PID return value is set to two different values in each process.

Tying standard streams


1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Child Process
// Tie the standard input, output and error streams to the
// appropiate pipe ends
// The file descriptor 0 is the standard input
// We tie it to the read end of the pipe as we will use
// this end of the pipe to read from it
// Same for the others
dup2 (CHILD_STDIN_READ,0);
dup2 (CHILD_STDOUT_WRITE,1); // standard output
dup2 (CHILD_STDERR_WRITE,2); // standard error
// Close in the child the unused ends of the pipes
close(CHILD_STDIN_WRITE);
close(CHILD_STDOUT_READ);
close(CHILD_STDERR_READ);

In each process, we only use one end of the pipes. This is, in the child, we only read from stdin and only write to stdout and stderr. So we close the other ends.

We do the same in the parent process but don’t need to tie anything this time.

exec()

In the child, we execute the program to be controlled through execl()


1
2
3
// Child
// Execute the program
execl("./program", "program", (char*)NULL);

When you call exec(), the called program replaces the executing program, so the streams of the called program are still tied to our pipes.

Sending messages

Everything is set up now! Let’s send messages!


1
2
3
4
5
6
7
8
9
10
11
12
13
// Parent
string message;

cout << "Parent: I'll send the child a message." << endl;

ofdstream in_stream(CHILD_STDIN_WRITE);
in_stream << "Hello Child!\n";

ifdstream out_stream(CHILD_STDOUT_READ);
getline(out_stream, message);

cout << "Parent: Child just said through stdout: " << endl
     << "\t\"" << message  << "\"" << endl;

We send the message “Hello Child!” through the write end of the child’s stdin pipe.

The child process gets it and outputs another message that we read from the read end of the child’s stdout pipe.

Pipes I/O  with file descriptors using C++ STL streams

You can see here two simple functions to read and write strings through pipes using file descriptors and C++ STL-like input/output streams.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
string read_from_pipe (int file_descriptor)
{
  // We create a C++ stream from a file descriptor
  // stdio_filebuf is not synced with stdio.
  // From GCC 3.4.0 on exists in addition stdio_sync_filebuf
  // You can also create the filebuf from a FILE* with
  // FILE* f = fdopen(file_descriptor, mode);
  __gnu_cxx::stdio_filebuf<char> filebuf(file_descriptor,
                                           std::ios_base::in);
  istream stream(&filebuf);
  // You can also do:
  // ostringstream stream;
  // stream << &filebuf;
  // return stream.str();

  string line;
  if (stream.good())
    getline(stream, line);

  return line;
}

1
2
3
4
5
6
7
8
9
void write_to_pipe (int file_descriptor, const string& line)
{
  __gnu_cxx::stdio_filebuf<char> filebuf(file_descriptor,
                                         std::ios_base::out);
  ostream stream(&filebuf);

  if (stream.good())
    stream << line;
}

I use the __gnu_cxx::stdio_filebuf class. This class is part of the GNU extensions of the libstdc++ library.

From the library documentation:

[This class] Provides a layer of compatibility for C/POSIX. This GNU extension provides extensions for working with standard C FILE*’s and POSIX file descriptors. It must be instantiated by the user with the type of character used in the file stream, e.g., stdio_filebuf<char>.

With this object, we can create a STL istream or ostream, giving its constructor the address of our object.

I’ve created two C++ classes, so we can interact with the child’s streams like they were C++ STL streams, instead of using these functions. You can see the interface here.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ifdstream : public _fdstream
{
public:
  ifdstream();
  ifdstream(int file_descriptor);
  void open(int file_descriptor);
  ifdstream& operator>> (string& str);
  size_t getline (char* s, streamsize n);
  size_t getline (char* s, streamsize n, char delim);
  ~ifdstream();
};

class ofdstream : public _fdstream
{
public:
  ofdstream();
  ofdstream(int file_descriptor);
  void open(int file_descriptor);
  ofdstream& operator<< (const string& str);
  ~ofdstream();
};

With these classes, you can create STL-like C++ streams from your file descriptors, so you can read and write from/to the pipes with the usual >> and << operators. I haven’t implemented more functions, as this is only a demonstration.

That’s all folks! You can now create families of talking processes!

Source code:

Repository in Github!: Repository link

Download the full source code

You can download the full source code of this example here:

C++ streams mini-library: ipc-fdstream.hpp (585)
Parent process source: ipc-parent.cpp (629)
Controlled program: ipc-program.cpp (451)
Makefile: ipc-makefile (332)

Also, all in a tarball: Tarball IPC-FDStream (386)

The code is in the public domain, you can do whatever you want with it.

I’d appreciate any improvement or comment.

Of course this code is for educational purposes only. If you want to do something serious about file descriptors and C++ streams, have a look at boost::iostreams::file_descriptor and boost::iostreams::stream.

Resources
Tags: , , ,
Categoría: Sin categoría
Puede seguir las respuestas a esta entrada a través del feed RSS 2.0. Puedes dejar una respuesta, o hacer trackback desde tu propio sitio.
Deja una respuesta

XHTML: Puedes usar estos tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>