I am currently trying to write a program in C which will read from two named pipes and print any data to stdout as it becomes available.
for example: If I open two terminals and ./execute pipe1 pipe2
in one of the terminals (with pipe1 and pipe2 being valid named pipes) and then type echo "Data here." > pipe1
then the name of the pipe (here it is pipe1
), the size, and the data should print to stdout-- Here it would look like pipe1 [25]: Data here.
I know I need to open the pipes with the O_RDONLY
and O_NONBLOCK
flags. I have looked at many examples (quite a few on this forum) of people using select()
and I still don't understand what the different parameters being passed to select()
are doing. If anyone can provide guidance here it would be hugely helpful. Below is the code I have so far.
int pipeRouter(char[] fifo1, char[] fifo2){
fileDescriptor1 = open(fifo1, O_RDONLY, O_NONBLOCK);
fileDescriptor2 = open(fifo2, O_RDONLY, O_NONBLOCK);
if(fileDescriptor1 < 0){
printf("%s does not exist", fifo1);
}
if(fileDescriptor2 < 0){
printf("%s does not exist", fifo2);
}
}
The select
lets you wait for an i/o event instead of waisting CPU cycles on read
.
So, in your example, the main loop can look like:
for (;;)
{
int res;
char buf[256];
res = read(fileDescriptor1, buf, sizeof(buf));
if (res > 0)
{
printf("Read %d bytes from channel1\n", res);
}
res = read(fileDescriptor2, buf, sizeof(buf));
if (res > 0)
{
printf("Read %d bytes from channel2\n", res);
}
}
If you add the code and run it, you would notice that:
To solve issue, select
and poll
APIs are introduced. For select
we need to know descriptors (we do), and the maximum out of them.
So let's modify the code a bit:
for (;;)
{
fd_set fds;
int maxfd;
FD_ZERO(&fds); // Clear FD set for select
FD_SET(fileDescriptor1, &fds);
FD_SET(fileDescriptor2, &fds);
maxfd = fileDescriptor1 > fileDescriptor2 ? fileDescriptor1 : fileDescriptor2;
select(maxfd + 1, &fds, NULL, NULL, NULL);
// The minimum information for select: we are asking only about
// read operations, ignoring write and error ones; and not
// defining any time restrictions on wait.
// do reads as in previous example here
}
When running the improved code, the CPU would not be wasted as much, but you will notice, that the read
operation is performed even when there is no data for a particular pipe, but there is for another.
To check, which pipe actually has the data, use FD_ISSET
after select
call:
if (FD_ISSET(fileDescriptor1, &fds))
{
// We can read from fileDescriptor1
}
if (FD_ISSET(fileDescriptor2, &fds))
{
// We can read from fileDescriptor2
}
So, after joining said above, the code would look like:
for (;;)
{
fd_set fds;
int maxfd;
int res;
char buf[256];
FD_ZERO(&fds); // Clear FD set for select
FD_SET(fileDescriptor1, &fds);
FD_SET(fileDescriptor2, &fds);
maxfd = fileDescriptor1 > fileDescriptor2 ? fileDescriptor1 : fileDescriptor2;
select(maxfd + 1, &fds, NULL, NULL, NULL);
if (FD_ISSET(fileDescriptor1, &fds))
{
// We can read from fileDescriptor1
res = read(fileDescriptor1, buf, sizeof(buf));
if (res > 0)
{
printf("Read %d bytes from channel1\n", res);
}
}
if (FD_ISSET(fileDescriptor2, &fds))
{
// We can read from fileDescriptor2
res = read(fileDescriptor2, buf, sizeof(buf));
if (res > 0)
{
printf("Read %d bytes from channel2\n", res);
}
}
}
So, add error handling, and you would be set.