Search code examples
c++socketsipcunix-socketsocketpair

C++ Socket Pair Not Reading/Writing Parent/Child


I've been assigned a project in which I need to use Unix Domain Sockets to communicate two-way between a parent and child process. My original approach was to create a child and server, but I was getting some serious issues with connectivity and paths. If anyone would prefer to see that code, I'm willing to provide it.

Anyway, I'm reaching the line in my program where I print out "parent has written", although I'm not entirely convinced that it has because no subsequent reading occurs. I've worked with closing the pipes in different places and changing how I'm reading/writing.

I am now using socketpair() to set up a pair of sockets. I cannot use any other type of IPC other than sockets. This information is brand new to me, so forgive the sloppy code.

Please ignore long #include list, will clean up ASAP.

Again, my current output stops at "Parent wrote to child" and then it keeps running, does not close out, so I think it's the child read waiting to receive something.

My program needs to be able to read and write incredibly long files, so you'll see serialization and parsing work throughout the code. The serialization and parsing works perfectly on a different part of the project, so I know that's not the issue. It's the darn sockets!

Anyway, code:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <iostream>
#include <algorithm>
#include <sys/wait.h>
#include <vector>
#include <fstream>

int main() {
    int sockets[2];

    std::cout << "Please enter name of text file." << std::endl;
    std::string entered_file;
    std::cin >> entered_file;
    std::string string_to_find;
    std::cout << "Please enter the string you'd like to search for." << std::endl;
    std::cin >> string_to_find;

    int rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
    if (rc < 0) {
        perror("socketpair");
        exit(1);
    }

    pid_t p;
    p = fork();

    if (p == 0)  //child
    {
        char ch;
        std::string the_file_as_string = "";
        std::vector<std::string> lines_with_string;

        int r;
        while ((r = read(sockets[0], &ch, 1)) > 0) {
            the_file_as_string.push_back(ch);
        }

        std::cout << "Child has read from parent..." << std::endl;

        size_t pos = 0;
        while(the_file_as_string.find(string_to_find, pos) != std::string::npos) {
            pos = the_file_as_string.find(string_to_find, pos+string_to_find.size());
            std::string sub = the_file_as_string.substr(0,pos);
            int occurrences = 0;
            size_t pos2 = 0;
            while (sub.find("/0", pos2 ) != std::string::npos) {
                pos2 = sub.find("/0", pos2+2);
                ++ occurrences;
                pos += string_to_find.length();
            }
            occurrences = occurrences;
            std::string occurrences_string = std::to_string(occurrences);
            lines_with_string.push_back(occurrences_string);
        }
        std::cout << "child has parsed" << std::endl;
        std::string lines_with_string_as_string = "";
        for(unsigned int i=0; i<lines_with_string.size(); i++) {
             lines_with_string_as_string = lines_with_string_as_string + lines_with_string.at(i) + "/0";
         }
        int file_size = lines_with_string_as_string.size();

        write(sockets[0], lines_with_string_as_string.c_str(), file_size+1);
        close(sockets[0]);
        std::cout << "child has sent back to parent..." << std::endl;
    }
    else  //parent
    {
        close(sockets[0]);
        std::ifstream myfile;

        //open file
        myfile.open(entered_file.c_str());

        if (!myfile) {
            std::cerr << "Unable to open file datafile.txt" << std::endl;
            return 1;   // call system to stop
        }


        //here, pass lines to child one by one and return true or false based on if it finds the string
        char lines_with_string[100];
        char ch;
        std::vector<std::string> the_file;
        std::vector<int> final_line_numbers;
        std::vector<std::string> final_lines;
        std::string str;
         while (std::getline(myfile, str))
         {
             the_file.push_back(str);
         }
         std::string the_file_as_string = "";

         for(unsigned int i=0; i<the_file.size(); i++) {
             the_file_as_string = the_file_as_string + the_file.at(i) + "/0";
         }

         int big_size = the_file_as_string.size();
         write(sockets[1], the_file_as_string.c_str(), big_size+1);

         std::cout << "Parent wrote to the child..." << std::endl;

         wait(NULL);

        std::string lines_with_string_as_string = "";
        int r;
        while ((r = read(sockets[1], &ch, 1)) > 0) {
            lines_with_string_as_string.push_back(ch);
        }
        close(sockets[1]);
        std::cout << "Parent has read from child..." << std::endl;

        std::string delimit = "/0";
        size_t pos3 = 0;
        while ((pos3 = lines_with_string_as_string.find(delimit)) != std::string::npos) {
            std::string token = lines_with_string_as_string.substr(0, pos3);
            int token2 = std::stoi(token);
            final_line_numbers.push_back(token2);
            lines_with_string_as_string.erase(0, pos3 + delimit.length());
        }
        //match line numbers to array of lines (original)
        for(unsigned int i=0; i < final_line_numbers.size(); i++) {
            int find_int = final_line_numbers.at(i);
            std::string find_string = the_file.at(find_int-1);
            std::cout << find_string << std::endl;
            final_lines.push_back(find_string);
        }

        std::sort(final_lines.begin(), final_lines.end());
        std::cout << "The final outcome with lines containing " << "'" << string_to_find << "' are:" << std::endl;
        for(unsigned int i=0; i<final_lines.size(); i++) {
            std::cout << final_lines.at(i) << std::endl;
        }
         myfile.close();
         std::cout << "Program has exited completely." << std::endl;
    }
}

MINI VERSION (same issue, says "Parent has written to child..." and then doesn't do anything further, and also doesn't exit:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <iostream>
#include <algorithm>
#include <sys/wait.h>
#include <vector>
#include <fstream>

int main() {

    int sockets[2];

    pid_t p;


    socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);

    p=fork();

    if (p < 0) {
        perror("forking");
    }
    else if(p > 0) {                                                //parent
        std::string long_string_to_send = "ladjfldjsfljasdfj adjlkjadlsjf a fljasdfladj ljdl aljfajdfljadfadsflajd  ajfdlkjslfjadj faldjsljalsdfljdfljadsl jladsfjasflajdflkajfl dasl fjalfjldjsfladsflajdlsfald fjljdfljadfjadfjl djfljadlfj aldjl hello";
        int big_size = long_string_to_send.size();

        write(sockets[1], long_string_to_send.c_str(), big_size+1);
        std::cout << "Parent has written to child..." << std::endl;

        wait(NULL);

        std::string lines_with_string_as_string = "";
        int r;
        char ch;
        while ((read(sockets[1], &ch, 1)) > 0) {
            lines_with_string_as_string.push_back(ch);
        }
        close(sockets[1]);
        std::cout << "Parent has received from child..." << std::endl;
        std::cout << "Parent has read the following... " << lines_with_string_as_string << std::endl;
        std::cout << "Program has exited completely" << std::endl;
    }
    else {                                                          //child
        char ch;
        std::string the_file_as_string = "";

        int r;
        while ((read(sockets[0], &ch, 1)) > 0) {
            the_file_as_string.push_back(ch);
        }

        std::cout << "Child has read from parent..." << std::endl;
        std::cout << "Child has read the following... " << the_file_as_string << std::endl;
        int file_size = the_file_as_string.size();

        write(sockets[0], the_file_as_string.c_str(), file_size+1);
        close(sockets[0]);
    }
}

Solution

  • Boiled down... your parent does:

    write(sockets[1], ...
    wait(NULL)
    

    While child does:

    while (read(sockets[0] ....)   > 0)
    ...
    

    The key to this problem is: why would read from sockets[0] ever return something less than or equal to zero. The answer here is: never. You're presumably intending to have the child read everything that the parent wrote to the socket, but the way sockets work is that you don't get an end-of-file indication until the other peer closed the socket (or does a shutdown(2).

    So in the parent you should do something like:

    write(sockets[1], ....);
    shutdown(sockets[1], SHUT_WR);             <<<<<=================
    wait(NULL)
    

    By calling shutdown with SHUT_WR on the parent's end, the child will get an EOF indication (return value of zero) on its end of the socket. Otherwise, the child's read will block forever (because how does the OS know if the parent would write more data later?).

    A little further explanation: the parent could also close its end of the socket, but if it does, it will not be able to read the response data from the child. shutdown essentially allows you to "half-close" the socket; that is close-for-writing but leave open for reading.

    The only real alternative to this approach is to "frame" the data in some way (i.e. creating a "protocol"). That is, you can first write the length of the data you will be sending in some fixed-size or otherwise distinct way, and the child could then read exactly the number of bytes needed. Then you can continue dialog between the two processes indefinitely without the need for shutdown or close.

    EDIT:
    One more caveat. It is best practice for each process, after the fork to close the other's side of the socket. In other words, just after forking, the parent would close sockets[0] and the child would close sockets[1]. Otherwise, a close from one side will not actually produce an end-of-file on the other side (because the file descriptor is still open in the reading side -- just after the fork both file descriptors are open in both processes). Using shutdown/SHUT_WR bypasses this difficulty because it explicitly signals that one side is to be considered closed for the purposes of writing.