Основываясь на аналогичном примере, расположенном здесь в stackoverflow, у меня есть три именованных канала: pipe_a, pipe_b и pipe_c, которые передаются из внешних процессов. Я хотел бы иметь процесс чтения, который выводится на консоль, независимо от того, что написано на любом из этих каналов.
Нижеприведенная программа - это программа "все-в-одном", которая должна читать три канала неблокируемым образом и отображать вывод, когда какая-либо из труб получает новые данные.
Однако он не работает - он блокируется! Если pipe_a получает данные, он отобразит их, а затем дождитесь появления новых данных в pipe_b и т.д....
select() должен позволять контролировать несколько дескрипторов файлов до тех пор, пока не будет готов, и в это время мы должны зайти в функцию чтения канала и получить данные.
Может ли кто-нибудь помочь определить, почему трубы ведут себя так, как будто они находятся в режиме блокировки?
/*
* FIFO example using select.
*
* $ mkfifo /tmp/fifo
* $ clang -Wall -o test ./test.c
* $ ./test &
* $ echo 'hello' > /tmp/fifo
* $ echo 'hello world' > /tmp/fifo
* $ killall test
*/
#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
// globals
int fd_a, fd_b, fd_c;
int nfd_a, nfd_b, nfd_c;
fd_set set_a, set_b, set_c;
char buffer_a[100*1024];
char buffer_b[100*1024];
char buffer_c[100*1024];
int readPipeA()
{
ssize_t bytes;
size_t total_bytes;
if (FD_ISSET(fd_a, &set_a)) {
printf("\nDescriptor %d has new data to read.\n", fd_a);
total_bytes = 0;
for (;;) {
printf("\nDropped into read loop\n");
bytes = read(fd_a, buffer_a, sizeof(buffer_a));
if (bytes > 0) {
total_bytes += (size_t)bytes;
printf("%s", buffer_a);
} else {
if (errno == EWOULDBLOCK) {
printf("\ndone reading (%ul bytes)\n", total_bytes);
break;
} else {
perror("read");
return EXIT_FAILURE;
}
}
}
}
}
int readPipeB()
{
ssize_t bytes;
size_t total_bytes;
if (FD_ISSET(fd_b, &set_b)) {
printf("\nDescriptor %d has new data to read.\n", fd_b);
total_bytes = 0;
for (;;) {
printf("\nDropped into read loop\n");
bytes = read(fd_b, buffer_b, sizeof(buffer_b));
if (bytes > 0) {
total_bytes += (size_t)bytes;
printf("%s", buffer_b);
} else {
if (errno == EWOULDBLOCK) {
printf("\ndone reading (%ul bytes)\n", total_bytes);
break;
} else {
perror("read");
return EXIT_FAILURE;
}
}
}
}
}
int readPipeC()
{
ssize_t bytes;
size_t total_bytes;
if (FD_ISSET(fd_c, &set_c)) {
printf("\nDescriptor %d has new data to read.\n", fd_c);
total_bytes = 0;
for (;;) {
printf("\nDropped into read loop\n");
bytes = read(fd_c, buffer_c, sizeof(buffer_c));
if (bytes > 0) {
total_bytes += (size_t)bytes;
printf("%s", buffer_c);
} else {
if (errno == EWOULDBLOCK) {
printf("\ndone reading (%ul bytes)\n", total_bytes);
break;
} else {
perror("read");
return EXIT_FAILURE;
}
}
}
}
}
int main(int argc, char* argv[])
{
// create pipes to monitor (if they don't already exist)
system("mkfifo /tmp/PIPE_A");
system("mkfifo /tmp/PIPE_B");
system("mkfifo /tmp/PIPE_C");
// open file descriptors of named pipes to watch
fd_a = open("/tmp/PIPE_A", O_RDWR | O_NONBLOCK);
if (fd_a == -1) {
perror("open");
return EXIT_FAILURE;
}
FD_ZERO(&set_a);
FD_SET(fd_a, &set_a);
fd_b = open("/tmp/PIPE_B", O_RDWR | O_NONBLOCK);
if (fd_b == -1) {
perror("open");
return EXIT_FAILURE;
}
FD_ZERO(&set_b);
FD_SET(fd_b, &set_b);
fd_c = open("/tmp/PIPE_C", O_RDWR | O_NONBLOCK);
if (fd_c == -1) {
perror("open");
return EXIT_FAILURE;
}
FD_ZERO(&set_c);
FD_SET(fd_c, &set_c);
for(;;)
{
// check pipe A
nfd_a= select(fd_a+1, &set_a, NULL, NULL, NULL);
if (nfd_a) {
if (nfd_a == -1) {
perror("select");
return EXIT_FAILURE;
}
readPipeA();
}
// check pipe B
nfd_b= select(fd_b+1, &set_b, NULL, NULL, NULL);
if (nfd_b) {
if (nfd_b == -1) {
perror("select");
return EXIT_FAILURE;
}
readPipeB();
}
// check pipe C
nfd_c= select(fd_c+1, &set_c, NULL, NULL, NULL);
if (nfd_c) {
if (nfd_c == -1) {
perror("select");
return EXIT_FAILURE;
}
readPipeC();
}
}
return EXIT_SUCCESS;
}
--- Обновлен код ---
Модифицированное приложение основано на обратной связи здесь, и еще немного чтения:
/*
* FIFO example using select.
*
* $ mkfifo /tmp/fifo
* $ clang -Wall -o test ./test.c
* $ ./test &
* $ echo 'hello' > /tmp/fifo
* $ echo 'hello world' > /tmp/fifo
* $ killall test
*/
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int readPipe(int fd)
{
ssize_t bytes;
size_t total_bytes = 0;
char buffer[100*1024];
printf("\nDropped into read pipe\n");
for(;;) {
bytes = read(fd, buffer, sizeof(buffer));
if (bytes > 0) {
total_bytes += (size_t)bytes;
printf("%s", buffer);
} else {
if (errno == EWOULDBLOCK) {
printf("\ndone reading (%d bytes)\n", (int)total_bytes);
break;
} else {
perror("read");
return EXIT_FAILURE;
}
}
}
return EXIT_SUCCESS;
}
int main(int argc, char* argv[])
{
int fd_a, fd_b, fd_c; // file descriptors for each pipe
int nfd; // select() return value
fd_set read_fds; // file descriptor read flags
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
// create pipes to monitor (if they don't already exist)
system("mkfifo /tmp/PIPE_A");
system("mkfifo /tmp/PIPE_B");
system("mkfifo /tmp/PIPE_C");
// open file descriptors of named pipes to watch
fd_a = open("/tmp/PIPE_A", O_RDWR | O_NONBLOCK);
if (fd_a == -1) {
perror("open");
return EXIT_FAILURE;
}
fd_b = open("/tmp/PIPE_B", O_RDWR | O_NONBLOCK);
if (fd_b == -1) {
perror("open");
return EXIT_FAILURE;
}
fd_c = open("/tmp/PIPE_C", O_RDWR | O_NONBLOCK);
if (fd_c == -1) {
perror("open");
return EXIT_FAILURE;
}
FD_ZERO(&read_fds);
FD_SET(fd_a, &read_fds); // add pipe to the read descriptor watch list
FD_SET(fd_b, &read_fds);
FD_SET(fd_c, &read_fds);
for(;;)
{
// check if there is new data in any of the pipes
nfd = select(fd_a+1, &read_fds, NULL, NULL, &tv);
if (nfd != 0) {
if (nfd == -1) {
perror("select");
return EXIT_FAILURE;
}
if (FD_ISSET(fd_a, &read_fds)) {
readPipe(fd_a);
}
}
nfd = select(fd_b+1, &read_fds, NULL, NULL, &tv);
if (nfd != 0) {
if (nfd == -1) {
perror("select");
return EXIT_FAILURE;
}
if (FD_ISSET(fd_b, &read_fds)){
readPipe(fd_b);
}
}
nfd = select(fd_c+1, &read_fds, NULL, NULL, &tv);
if (nfd != 0) {
if (nfd == -1) {
perror("select");
return EXIT_FAILURE;
}
if (FD_ISSET(fd_c, &read_fds)){
readPipe(fd_c);
}
}
usleep(10);
}
return EXIT_SUCCESS;
}
Все еще проблема с выбором return zero (0), когда есть данные, ожидающие в любой из наблюдаемых труб? Я не должен правильно использовать функции select() и fd_isset(). Вы видите, что я делаю неправильно? Благодарю.
Проблема в том, что функция select блокируется. Я понял select(), чтобы проверять флаги, чтобы увидеть, будет ли чтение "блокироваться", если оно было выполнено, чтобы можно было выполнить чтение или нет. Труба открывается в режиме RDWR и NONBLOCK.
Вы говорите, что проблема заключается в том, что функция select блокируется, но продолжайте признавать, что флаг NONBLOCK
делает это так, чтобы чтение блокировалось. Выбор и чтение - это две разные вещи.
Флаг O_NONBLOCK
влияет на сокет (и, следовательно, ваши read
вызовы); он не изменяет поведение select
, которое имеет свою собственную тайм-аут/блокировку семантики.
man select
утверждает, что аргумент timeout
с обоими числовыми членами, установленными на ноль, производит неблокирующий опрос, тогда как аргумент таймаута NULL
может привести к неопределенному блоку:
Если параметр тайм-аута является нулевым указателем, то вызов pselect() или select() должен блокироваться неограниченно, пока, по крайней мере, один дескриптор не выполнит указанные критерии. Чтобы осуществить опрос, параметр времени ожидания не должен быть пустым указателем, и должен указывать на нулевую стоимость
TimeSpecструктуры формата: первый формата.
(NB текст далее вверх страницы показывает, что, хотя pselect()
принимает структуру timespec
, select()
принимает структуру timeval
, я timeval
применить эту логику к приведенной выше цитате.)
Таким образом, перед каждым вызовом select
timeval
, установите его члены в ноль и передайте это для select
.
Несколько нот, пока мы здесь:
В идеале у вас будет только один вызов select
, одновременно проверяющий все три дескриптора файла, а затем решая, какие каналы read
, проверяя ваш набор FD с помощью fd_isset
;
Я также предлагаю немного usleep
в конце вашего тела цикла, иначе ваша программа начнет вращаться действительно, очень быстро, когда голодает информация.
select
с 3 отдельными наборами. Они должны быть объединены в один.
Я взял фрагмент, который я использовал для программирования сокетов, но он должен работать одинаково для именованных каналов. Это должно быть просто и легко следовать.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
int main()
{
fd_set readSet, writeSet, exSet;
struct timeval tv;
int i;
int fifoFds[3];
//open files or named pipes and put them into fifoFds array
while(1)
{
FD_ZERO(&readSet);
FD_ZERO(&writeSet); //not used
FD_ZERO(&exSet); //not used
int maxfd = -1;
for(i = 0; i < 3; i++)
{
if(maxfd == -1 || fifoFds[i] > maxfd)
maxfd = fifoFds[i];
FD_SET(fifoFds[i], &readSet);
}
tv.tv_sec = 1; //wait 1 second in select, change these as needed
tv.tv_usec = 0; //this is microseconds
select(maxfd+1, &readSet, &writeSet, &exSet, &tv);
for(i = 0; i < 3; i++)
{
if(FD_ISSET(fifoFds[i], &readSet))
{
//Read from that fifo now!
}
}
}
return 0;
}
Просто для упрощения вашего кода. Вам не нужно три выбора. Вы можете установить все дескрипторы файлов с тремя вызовами FD_SET()
, выбрать вызов, а if nfd > 0
проверьте каждый fd_x with FD_ISSET()
.
Вот мое рабочее решение для чтения трех именованных каналов. Его можно было бы оптимизировать несколькими способами, но, как написано, для всех, кто должен это сделать, должно быть очень ясно:
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int readPipe(int fd)
{
ssize_t bytes;
size_t total_bytes = 0;
char buffer[100*1024];
printf("\nReading pipe descriptor # %d\n",fd);
for(;;) {
bytes = read(fd, buffer, sizeof(buffer));
if (bytes > 0) {
total_bytes += (size_t)bytes;
printf("%s", buffer);
} else {
if (errno == EWOULDBLOCK) {
break;
} else {
perror("read error");
return EXIT_FAILURE;
}
}
}
return EXIT_SUCCESS;
}
int main(int argc, char* argv[])
{
int fd_a, fd_b, fd_c; // file descriptors for each pipe
int nfd; // select() return value
fd_set read_fds; // file descriptor read flags
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
// create pipes to monitor (if they don't already exist)
system("mkfifo /tmp/PIPE_A");
system("mkfifo /tmp/PIPE_B");
system("mkfifo /tmp/PIPE_C");
// open file descriptors of named pipes to watch
fd_a = open("/tmp/PIPE_A", O_RDWR | O_NONBLOCK);
if (fd_a == -1) {
perror("open error");
return EXIT_FAILURE;
}
fd_b = open("/tmp/PIPE_B", O_RDWR | O_NONBLOCK);
if (fd_b == -1) {
perror("open error");
return EXIT_FAILURE;
}
fd_c = open("/tmp/PIPE_C", O_RDWR | O_NONBLOCK);
if (fd_c == -1) {
perror("open error");
return EXIT_FAILURE;
}
for(;;)
{
// clear fds read flags
FD_ZERO(&read_fds);
// check if there is new data in any of the pipes
// PIPE_A
FD_SET(fd_a, &read_fds);
nfd = select(fd_a+1, &read_fds, NULL, NULL, &tv);
if (nfd != 0) {
if (nfd == -1) {
perror("select error");
return EXIT_FAILURE;
}
if (FD_ISSET(fd_a, &read_fds)) {
readPipe(fd_a);
}
}
// PIPE_B
FD_SET(fd_b, &read_fds);
nfd = select(fd_b+1, &read_fds, NULL, NULL, &tv);
if (nfd != 0) {
if (nfd == -1) {
perror("select error");
return EXIT_FAILURE;
}
if (FD_ISSET(fd_b, &read_fds)){
readPipe(fd_b);
}
}
// PIPE_C
FD_SET(fd_c, &read_fds);
nfd = select(fd_c+1, &read_fds, NULL, NULL, &tv);
if (nfd != 0) {
if (nfd == -1) {
perror("select error");
return EXIT_FAILURE;
}
if (FD_ISSET(fd_c, &read_fds)){
readPipe(fd_c);
}
}
usleep(100000);
}
return EXIT_SUCCESS;
}