c - 准备好后不会读取命名管道。 (它确实在gdb内部工作)

标签 c unix pipe named-pipes fifo

2014年12月20日更新:此问题已解决,请参阅问题底部的工作代码。

设计

有四个客户端处理一些数据,然后通过命名管道 (FIFO) 将其传递到服务器进程。

问题

当在 gdb 之外运行服务器时(不进入 gdb 也会出现同样的问题),仅读取一个管道。 Select 返回 1,FD_ISSET 仅对一个管道使用react(并且在执行期间保持同一管道)。

查看/proc/[PID]/{fd,fdinfo} 显示其他管道仍然打开并且尚未被读取。 fdinfo 中的 pos 字段为 0)。

问题

我需要更改什么才能以交错方式读取所有四个管道?

测试

为了模拟客户端,我使用了一个 12MByte 的随机文件,该文件被cat放到命名管道上。

随机文件的生成方式为:

dd if=/dev/urandom of=test.bin bs=1024 count=$((1024*12))

然后执行为(每个在单独的终端中并按以下顺序)

terminal 1:
./server.out
terminal 2:
cat test.bin > d0
terminal 3:
cat test.bin > d1
terminal 4:
cat test.bin > d2
terminal 5:
cat test.bin > d3

Makefile

server:
    gcc server.c -o server.out -g -D _DEFAULT_SOURCE -Wall --std=c11

来源

客户端称为加密狗。

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

#define NR_OF_DONGLES 4

int do_something(int fd);

int main()
{
    fd_set read_fd_set;
    FD_ZERO(&read_fd_set);

    int dongles[NR_OF_DONGLES];
    /*Create FIFO */
    for(int i = 0; i < NR_OF_DONGLES; i++)
    {
        char name[255];
        snprintf(name, sizeof(name), "d%d", i);
        if(mkfifo(name, 0666) == -1)
        {
            fprintf(stderr, "Failed to create fifo %s \t Error: %s", name, name);
            exit(EXIT_FAILURE);
        }

        int dongle = open(name, O_RDONLY);
        if(dongle > 0)
        {
            fprintf(stderr,"set dongle %s\n", name);
            FD_SET(dongle, &read_fd_set);
            dongles[i] = dongle;
        }
        else
        {
            fprintf(stderr, "failed to open: %s\nerror: %s\n", name, strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    int closed = 0;
    int isset[NR_OF_DONGLES];
    memset(isset, 0, sizeof(isset));

    while(closed < NR_OF_DONGLES)
    {
        int active;
        if((active = select (FD_SETSIZE , &read_fd_set, NULL,NULL,NULL)) < 0)
        {
            fprintf(stderr, "select failed\n errno: %s\n",strerror(errno));
            exit(EXIT_FAILURE);
        }
        fprintf(stderr, "active devices %i\n", active);

        for(int i = 0; i < NR_OF_DONGLES; ++i)
        {
            int dongle = dongles[i];
            if(FD_ISSET(dongle, &read_fd_set))
            {

                isset[i] += 1;
                int size = do_something(dongle);
                fprintf(stderr, "round %i \tdongle %i \tread %i bytes\n", isset[i],i, size);
                if(size == 0)
                {

                    if(close(dongle) == -1)
                    {
                        fprintf(stderr,"Could not close dongle %i\nError: %s\n",
                                i,strerror(errno));
                    }

                    closed += 1;
                    fprintf(stderr, "closed dongle %i \t number of closed dongles %i\n",
                            i, closed);

                    FD_CLR(dongle, &read_fd_set);
                }

            }   
        }
    }

    exit(EXIT_SUCCESS);
}

#define BLOCK_SIZE (8*1024)
/*
 * If the size is zero we reached the end of the file and it can be closed
 */
int do_something(int fd)
{
    int8_t buffer[BLOCK_SIZE];
    ssize_t size = read(fd, buffer, sizeof(buffer));
    if(size > 0)
    {
        //Process read data
    }
    else if(size == -1)
    {
        fprintf(stderr, "reading dongle failed\nerrno: %s", strerror(errno));
        return -1;
    }

    return size;
}

解决方案

kestasx 的解决方案对我有用。在调用 select 之前需要重新初始化监视列表 (read_fd_set)。

源代码

while(closed < number_of_dongles)
{
    /*Reinitialize watchlist of file descriptors.*/
    FD_ZERO(&read_fd_set);
    for(int i = 0; i < number_of_dongles; i++)
    {
        int dongle = dongles[i];
        /*if fd == -1 the pipe has been closed*/
        if(dongle != -1)
        {
            FD_SET(dongle, &read_fd_set);
        }
    }

    int active = select (FD_SETSIZE , &read_fd_set, NULL,NULL,NULL);
    if(active < 0)
    {
        fprintf(stderr, "select failed\n errno: %s\n",strerror(errno));
        exit(EXIT_FAILURE);
    }
    //fprintf(stderr, "active devices %i\n", active);

    for(int i = 0; i < number_of_dongles; ++i)
    {
        int dongle = dongles[i];
        /*Check if the current dongle fd has data in the FIFO*/
        if(FD_ISSET(dongle, &read_fd_set))
        {
            isset[i] += 1;
            int size = transfer_dongle_data(dongle);
    //      fprintf(stderr, "round %i \tdongle %i \tread %i bytes\n", isset[i],i, size);
            if(size == 0)
            {

                if(close(dongle) == -1)
                {
                    fprintf(stderr,"Could not close dongle %i\nError: %s\n",
                            i,strerror(errno));
                }

                closed += 1;
                fprintf(stderr, "closed dongle %i \t number of closed dongles %i\n",
                        i, closed);

                FD_CLR(dongle, &read_fd_set); //could be removed
                /*notify that the pipe is closed*/
                dongles[i] = -1;
            }

        }   
    }
}

最佳答案

您可以尝试通过 strace 运行您的代码(Solaris 上为 truss,FreeBSD 上为 ktrace/kdump)。对我来说,它在 open("d0", O_RDONLY) 上停止。因此服务器之前不会创建所有管道(其他管道很可能是由 cat 创建的)。

if(dongle)... open 后不正确:如果失败,open() 返回 -1,而不是 0。

因此,我认为您的程序无法处理您期望的文件(仅正确打开一个管道)。

还有一个与 select() 使用相关的问题。您应该在每次调用 select() 之前重新初始化 read_fd_set,因为在每次 select() 调用之后,仅包含有数据的描述符被标记,其他的都清除了。

关于c - 准备好后不会读取命名管道。 (它确实在gdb内部工作),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27487412/

相关文章:

unix - 从文本文件中提取第一列和最后一列

用于 unix 'at' 命令的 Ruby gem

c - 双向 popen() 在 C 中的 Mac OS X 上工作吗?

function - 你如何编写一个从管道输入读取的powershell函数?

c - 在 C 中给定开始和结束内存地址的意外行为遍历数组

c# - 将 C# 集成到 C 到 Java 时出现 OutOfMemoryError/程序崩溃

c - 理解 libm 中日志操作中的数字文字

c - 为什么这段代码可重入但不是线程安全的

linux - 运行位于/bin中的自定义程序

Linux命令输出作为另一个命令的参数