How to send a struct over a pipe C++

2019-05-30 10:34发布

问题:

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);
}

回答1:

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 strings 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

  1. Write message.from to pipe.
  2. Write length of message.msg to pipe.
  3. Write message.msg.data() to pipe.

Two caveats:

  1. 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.
  2. 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.

  1. Loop on read until all of message.from has arrived.
  2. Loop on read until all of the length of message.msg has arrived.
  3. Use message.msg.resize(length) to size message.msg to hold the message.
  4. Loop on read until all of message.msg has arrived. You can read the message directly into message.msg.data().


标签: c++ struct