【问题标题】:read() stdout from child in infinite loop无限循环中从子级读取()标准输出
【发布时间】:2018-02-28 04:48:22
【问题描述】:

我正在分叉一个孩子,它将运行一个无限循环并做一些事情。当孩子处于“就绪”状态时,文本将使用 printf 打印到标准输出。我正在用管道重定向孩子的标准输出,以便父母可以看到它。当此文本打印时,我希望父级被解除阻止。

问题似乎是即使在 printf() 调用之后 read() 调用也会保持阻塞。

这是我正在使用的一些示例代码:

父母:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int fd[2];
    pipe(fd);
    char buffer[256];

    switch(fork())
    {
        case -1:
            printf("fork failed\n");
            _exit(1);
        case 0:
            printf("Starting progtest...\n");
            close(fd[0]);
            dup2(fd[1], STDOUT_FILENO);
            close(fd[1]);
            execl("progtest", "progtest", NULL);

            perror("exec failed");
            _exit(1);
    }

    close(fd[1]);
    read(fd[0], &buffer, sizeof(buffer));
    close(fd[0]);

    printf("buffer: %s\n", buffer);

}

孩子:

#include <stdio.h>

int main()
{
    printf("ready\n");
    while(1)
    {
        // stuff happens here
    }

    return 0;
}

我很确定管道设置正确。如果我删除无限循环,父级将解除阻塞并打印“就绪”。

有什么方法可以在孩子还在跑步时使用 read() 吗?我是否通过使用管道错误地解决了这个问题?我需要使用两条不同的状态消息执行此操作两次。

【问题讨论】:

  • 可能是缓冲问题。在每个printf 之后尝试fflush(stdout)
  • @melpomene 这适用于这个示例,但我希望得到一个不会对子源进行大量更改的答案(整个循环中有很多打印语句)
  • 您可以使用setvbuf(stdout, NULL, _IONBF, 0); 完全关闭输出缓冲。
  • @Geoffrey 这两个解决方案都解决了示例代码的问题,但它们似乎并没有做我正在寻找的事情(而且看起来 read 函数并不意味着工作我想要的方式)。当我在我的实际子程序上尝试这个时,读取将挂起等待更多的写入。我想读取在下一个写入标准输出的实例上返回,而不是所有未来的输出到标准输出
  • 啊,我明白了。 read 将读取最多 N 个字节或直到 EOF,您可能想要查看 getline。如果失败,您可以使用select 在尝试读取之前确定文件描述符上是否有要读取的数据。从文件描述符读取时,无论是文件、套接字还是管道,您都必须考虑读取比您想要的更多的数据。通常这是通过在每条消息中发送一个包含消息长度的标头来完成的。我会用一个例子提出一个答案。

标签: c pipe fork parent-child


【解决方案1】:

您似乎正在尝试执行进程间通信 (IPC)。读/写没有一对一的通信同步,父或子可以一次写入尽可能多的文件描述符,并且由于它们是异步运行的,因此您必须确保您已准备好处理此问题。

通常这是通过在每条消息中发送一个固定大小的标头来完成的,其中包含有效负载的长度。例如:

协议头

#include <stdint.h>

struct Message
{
  uint8_t length; // assuming a maximum message length of 255 bytes, adjust as needed.
};

你可以在这里定义额外的东西,通常最好包含版本,也许是序列 ID 等。重要的是,如果你通过网络进行通信,这个标头的大小在所有硬件实现上都是绝对的,所以需要使用 stdint 定义来确保架构之间的兼容性。如果跨架构工作,确保字节序一致也是一个好主意。

发送消息

const char data[] = "example";

// create a buffer large enough to pack the header and data into it
const uint8_t len = strlen(data) + 1;
char buffer[sizeof(struct Message) + len];

// populate the header
struct Message *m = (struct Message *)buffer;
m->length = len;

// copy in the payload after the header
memcpy(buffer + sizeof(struct Message), data, len);

// write the packed buffer
write(fd, buffer, sizeof(struct Message) + len);

阅读消息

struct Message m;
if (read(fd, &m, sizeof(struct Message)) != sizeof(struct Message))
{
  fprintf(stderr, "Failed to read the header\n");
  exit(1);
}

// If required perform validation on the header here

ssize_t offset = 0;
char data[m.length];
while(offset < m.length)
{
  ssize_t r = read(fd, data + offset, m.length - offset);
  if (r < 0)
  {
    fprintf(stderr, "Read of message body failed\n");
    exit(1);
  }

  if (r == 0)
  {
    fprintf(stderr, "The sender closed the connection\n");
    exit(1);
  }

  offset += r;
}

printf("Message Length: %u, Message Data: %s\n", m.length, data);

需要使用 while 循环来读取,因为如果发送方超出写入缓冲区并且现在正在阻塞等待写入剩余部分,则数据可能已被分割成块。标头也可以跨块拆分,理想情况下也应该使用相同的技术读取,但为简洁起见,我省略了这一点。

【讨论】:

  • 这太完美了!这正是我想要实现的,当然值得更新子源。我看到一个小问题,接收到的数据打印出 2 个额外字符:“@”。我认为这可以多次使用吗?非常感谢,我将此标记为答案。如果我可以 +1,我会的。
  • 请注意我纠正了代码中的一些错误,如果您没有尝试更正,它可能会解释您的 2 个额外字符问题。
  • 哦,是的,这可以重复使用,这就是整个想法。消息边界定义明确,可以毫无问题地单独提取每条消息。如果您有多个写入器,但您可能希望更改写入以在一个操作中写入标头和数据,以避免出现竞争条件,即第二个写入器可以在第一个写入器写入其有效负载之前写入标头。编辑:实际上我会更新示例来做到这一点。
  • 我让它像我想要的那样工作了两次,而且效果很好!当我在字符串的开头写一个额外的字符时,我注意到在孩子方面。我认为这发生在 memcpy 线上。当我调整它时,主角将不再存在,但读取调用将挂起:(
  • 我看不出您会看到这种行为的任何原因,请使用示例代码打开一个新问题,我会看看您现在描述的问题与您的原始问题无关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-07
  • 1970-01-01
  • 1970-01-01
  • 2019-11-25
  • 1970-01-01
  • 2012-03-12
相关资源
最近更新 更多