I am very new to writing in c++ and am working on using pipes to communicate between processes. I have written a very simple program that works when I am sending strings or integers but when I try to send a struct (message in this case) I get null when I try to read it on the other side. Does anyone have some insight into this that they would share? Thanks for your time.
#include <unistd.h>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUFFER_LEN sizeof(message)
using namespace std;
struct message{
int from;
string msg;
};
void childCode(int *pipeOUT, int *pipeIN, message buffer){
// Local Buffer for input from pipeIN
cout << "Child: Sending Message"<< endl;
buffer.msg = "Child:I am the child!!";
write(pipeOUT[1],(char*) &buffer, BUFFER_LEN); // Test Child -> Parent comms
cout << "Child: Message Sent"<<endl;
read(pipeIN[0],(char*) &buffer,BUFFER_LEN); // Test Child <- Parent comms
cout << "Child: Recieved: "<< buffer.msg << endl;
cout << "Child Exiting..."<< endl;
exit(0); // Child process End
}
int main(int argCount, char** argVector){
pid_t pid;
int childPipeIN[2];
int childPipeOUT[2];
message buffer; // Buffer for reading from pipe
// Make Parent <- Child pipe
int ret = pipe(childPipeIN);
if (ret == -1){
perror("There was an error creating the childPipeIN. Exiting...");
exit(1);
}
// Make Parent -> Child pipe
ret = pipe(childPipeOUT);
if (ret == -1){
perror("There was an error creating the childPipeOUT. Exiting...");
exit(1);
}
// Fork off Child
pid = fork();
if (pid == -1){
perror("There has been an issue forking off the child. Exiting...");
exit(1);
}
if (pid == 0){ // Child code
cout << "Child PID = " << getpid() << endl;
childCode(childPipeIN,childPipeOUT,buffer);
}
else{ // Parent Code
cout << "Parent PID = " << getpid() << endl;
// Test Parent <- Child comms
read(childPipeIN[0], (char*) &buffer, BUFFER_LEN);
cout << "Parent: I recieved this from the child...\n" << buffer.msg << endl;
buffer.msg = "Parent: Got you message!";
// Test Parent -> Child comms
write(childPipeOUT[1], (char*) &buffer, BUFFER_LEN);
wait(null);
cout << "Parent: Children are done. Exiting..." << endl;
}
exit(0);
}
Yeah. I voted to close. Then I read Dupe more closely and realized it didn't explain the problem or the solution very well, and the solution didn't really fit with OP's intent.
The problem:
One does not simply write a std::string
into a pipe. std::string
is not a trivial piece of data. There are pointers there that do not sleep.
Come to think of it, it's bloody dangerous to write a std::string
into anything. Including another std::string
. I would not, could not with a file. This smurf is hard to rhyme, so I'll go no further with Dr. Seuss.
To another process, the pointer that references the storage containing the string
's data, the magic that allows string
s to be resizable, likely means absolutely nothing, and if it does mean something, you can bet it's not something you want to mess with because it certainly isn't the string
's data.
Even in the same process in another std::string
the two strings cannot peacefully co-exist pointing to the same memory. When one goes out of scope, resizes, or does practically anything else that mutates the string
badness will ensue.
Don't believe me? Check BUFFER_LEN
. No matter how big your message gets, BUFFER_LEN
never changes.
This applies to everything you want to write that isn't a simple hunk of data. Integer, write away. Structure of integers and an array of characters of fixed size, write away. std::vector
? No such luck. You can write std::vector::data
if and only if whatever it contains is trivial.
std::is_pod
may help you decide what you can and cannot read and write the easy way.
Solution:
Serialize the data. Establish a communications protocol that defines the format of the data, then use that protocol as the basis of your reading and writing code.
Typical solutions for moving a string
are null terminating the buffer just like in the good ol' days of C and prepending the size of the string
to the characters in the string
like the good old days of Pascal.
I like the Pascal approach because it allows you to size the receiver's buffer ahead of time. With null termination you have to play a few dozen rounds of Getta-byte looking for the null terminator and hope your buffer's big enough or compound the ugliness with the dynamic allocation and copying that comes with buffer resizes.
Writing is pretty much what you are doing now, but structure member by structure member. In the above case
- Write
message.from
to pipe.
- Write length of
message.msg
to pipe.
- Write
message.msg.data()
to pipe.
Two caveats:
- Watch your endian! Firmly establish the byte order used by your protocol. If the native endian does not match the protocol endian, some bit shifting may be required to re-orient the message.
- One man's
int
may be the size of another man's long
so use fixed width integers.
Reading is a bit more complicated because a single call to read will return up to the requested length. It may take more than one read to get all the data you need, so you'll want a function that loops until all of the data arrives or cannot arrive because the pipe, file, socket, whatever is closed.
- Loop on read until all of
message.from
has arrived.
- Loop on read until all of the length of
message.msg
has arrived.
- Use
message.msg.resize(length)
to size message.msg
to hold the message.
- Loop on read until all of
message.msg
has arrived. You can read the message directly into message.msg.data()
.