6
votes

I'm trying to realise a IPC between two different programs running on the same machine (in my case its a CentOS7). To have just a kind of loose coupling I decided to use a named pipe for the IPC. Therefore I'm was playing with the following example and ran into different problems.

Creating and writing into the pipe:

#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>

using namespace std;

main()  {
 int fd;
 char * myfifo = new char [12];
 strcpy(myfifo, "./tmp/myfifo1");

 /* create the FIFO (named pipe) */
 mkfifo(myfifo, 0666);
 /* write "Hi" to the FIFO */
 fd = open("./tmp/myfifo1", O_WRONLY ); //open(myfifo, O_WRONLY | O_NONBLOCK);
 if (fd == -1) {
     perror("open");
     return EXIT_FAILURE;
 } 
 printf("File open\n");
 write(fd, "entry [1]", sizeof("entry [1]"));
 sleep(1);
 write(fd, "entry [2]", sizeof("entry [2]"));
 sleep(2);
 write(fd, "entry [3]", sizeof("entry [3]"));
 printf("Content written\n");
 close(fd);
 printf("Connection closed\n");
 /* remove the FIFO */
 unlink(myfifo);
 return 0;
}

Reading the pipe:

#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <string>
#include <iostream>

using namespace std;

main()  {
 int fd; 
 fd_set set_a;

 char * myfifo = new char [12];
 strcpy(myfifo, "./tmp/myfifo1");
 char buffer[1024];

 fd = open("./tmp/myfifo1", O_RDONLY | O_NONBLOCK);
 if (fd == -1) {
     perror("open");
     return EXIT_FAILURE;
 } 
 ssize_t bytes;
 size_t total_bytes = 0;
 printf("\nDropped into read pipe\n");
 while(1){
     if((bytes = read(fd, buffer, sizeof(buffer))) > 0){
         std::string message(&buffer[22]);
         total_bytes += (size_t)bytes;
         printf("%i", bytes);

         printf("Message: %s\n", message.c_str());
         memset(&buffer[0], 0, sizeof(buffer));
     }else{
         if (errno == EWOULDBLOCK) {
             printf("\ndone reading (%d bytes)\n", (int)total_bytes);
             //break;
         }
         printf("No message\n");
         sleep(2);
     } 
 }
 return EXIT_SUCCESS;
}

I feel like named pipes are pretty unflexible in its behavior I figured out with my test programs. First of all if no reading process is attached to the fifo pipe all messages except of the last one written to the pipe get lost (or generally speaking only the last message can be read after the reading process is attached to the pipe). If you write multiple messages into the pipe all messages betweem the reading (e.g. polled) will be interpreted as one single message (I'm aware that they can be splitted by \0).

The main goals for the named pipe is a) system logs and b) kind of user authentication. The asynchronous of named pipes fits perfectly to my need. But anyway, I'm not sure if named pipes are the best solution for IPC between different programs. Also I'm not sure if the behavior descripted above is normal or if I'm using named pipe in a wrong way. I also thought about sockets but then I will run into huge blocking problems.

Thanks for your help.

2
Take a look that the following question stackoverflow.com/q/22714816/540286Ortwin Angermeier
decoupling + async messages + persistence = messaging. Give a try to a messaging solution - see RabbitMQ, it's included in current CentOS distributions.Sigi

2 Answers

4
votes

"First of all if no reading process is attached to the fifo pipe all messages except of the last one written to the pipe get lost".

No, they don't. Use cat instead of your (lousily written :D) reading process and you'll get all of the messages.

You are right. Pipes are byte-oriented, not message oriented. If you want messages, there's other IPC for that (e.g., SysV message queues).

Beej's Guide to Unix IPC is an excellent intro to Unix ipc, if you want something more advanced, then Advanced Programming in the Unix Environment by Richard Stevens is very good.

As far as user authentication is concerned, check out Unix sockets.

3
votes

Looks like you're trying to use pipes for what they were not designed for. First of all, there have to be reading process "attached" for the other side of the pipe. If you try to open pipe for writing and there is no reading process, open will hang waiting for it or return -1 with errno set to ENXIO (when O_NONBLOCK flag is used). So named pipe is a kind of randez-vous point in the system, where two processes met to exchange data ;)

Second - do not treat writing to a pipe as sending a message. Pipe is rather a "stream of bytes". You can write 10 bytes once, and the reader can do 5 reads of 2 bytes each, or vice-versa: you can write 5 times 2 bytes, and the reader can read them at once. So if you plan to send some kind of "messages" over the pipe you should develop some kind of minimal transport protocol (for example use '\0' byte as a delimiter, as you mentioned above).

So, maybe you should look at System V message queues (see msgget, msgctl, msgrcv, msgsnd, or POSIX message queues (see mq_open, mq_unlink, mq_send, mq_receive etc.), which gives you possibility to send and receive 'atomic' messages.