每次只从管道中读取一条“消息”?

时间:2013-05-12 17:44:47

标签: multithreading unix io pipe

我有两个与管道连接的进程。

一个进程有几个线程在管道上写消息。

另一个进程读取管道并处理消息。

问题是,当进程读取管道时,它会一个接一个地获取所有消息。 ¿有没有办法一次只读一条消息?

首先,我使用了写入和读取功能,直接使用文件描述符。 然后我尝试使用fdopen,fread和fwrite将它们视为文件,但它仍然同时读取所有数据。

邮件的大小每次都可以更改,因此我无法通过读取固定数量的字符来修复它。

1 个答案:

答案 0 :(得分:1)

很久以前,在世界任何地方POSIX都是一个已知概念之前的一段时间内,至少有一个版本的Unix维护了这样的东西,这样写入的内容大小小于管道缓冲区中剩余的空间大小。原始块对应于写入管道的数据包的大小,受制于您尝试读取足够数据的约束。不幸的是(或者我的意思是“很明显”),我再也无法证明情况就是这样 - 我已经有超过四分之一世纪无法访问相关的硬件和O / S.

然而,反例的证明表明,Mac OS X不再像这样处理读取端(尽管如果写入的请求大小足够小,POSIX确保write()调用是原子的)。这对我来说是个惊喜。

反例 - 代码

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

static void childish(int fd)
{
    char buffer[1024];
    int  nbytes;
    int  pid = getpid();
    while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
    {
        printf("%.5d: %.4d <<%.*s>>\n", pid, nbytes, nbytes, buffer);
        fflush(stdout);
    }
    printf("%.5d: exiting\n", pid);
    exit(0);
}

static void parental(int fd)
{
    char message[] = "The Quick Brown Fox Jumped Over The Lazy Dog";
    int  pid = getpid();
    for (int i = 0; i < 20; i++)
    {
        int  nbytes = rand() % (sizeof(message) - 1);
        while (nbytes == 0)
            nbytes = rand() % (sizeof(message) - 1);
        if (write(fd, message, nbytes) != nbytes)
            break;
        printf("%.5d: %.4d <<%.*s>>\n", pid, nbytes, nbytes, message);
        fflush(stdout);
    }
    printf("%.5d: exiting\n", pid);
    exit(0);
}

int main(void)
{
    int fd[2];
    pipe(fd);
    pid_t pid = fork();
    if (pid < 0)
        fprintf(stderr, "failed to fork\n");
    else if (pid == 0)
    {
        close(fd[1]);
        childish(fd[0]);
    }
    else
    {
        close(fd[0]);
        parental(fd[1]);
    }
    return EXIT_FAILURE;  // Failed to fork
}

反例 - 数据

在Mac OS X 10.8.3上测试。

86504: 0043 <<The Quick Brown Fox Jumped Over The Lazy Do>>
86504: 0001 <<T>>
86504: 0033 <<The Quick Brown Fox Jumped Over T>>
86504: 0006 <<The Qu>>
86504: 0030 <<The Quick Brown Fox Jumped Ove>>
86504: 0036 <<The Quick Brown Fox Jumped Over The >>
86504: 0024 <<The Quick Brown Fox Jump>>
86504: 0022 <<The Quick Brown Fox Ju>>
86504: 0031 <<The Quick Brown Fox Jumped Over>>
86504: 0037 <<The Quick Brown Fox Jumped Over The L>>
86504: 0028 <<The Quick Brown Fox Jumped O>>
86504: 0017 <<The Quick Brown F>>
86504: 0032 <<The Quick Brown Fox Jumped Over >>
86504: 0038 <<The Quick Brown Fox Jumped Over The La>>
86504: 0019 <<The Quick Brown Fox>>
86504: 0007 <<The Qui>>
86504: 0023 <<The Quick Brown Fox Jum>>
86504: 0005 <<The Q>>
86504: 0020 <<The Quick Brown Fox >>
86504: 0004 <<The >>
86504: exiting
86505: 0456 <<The Quick Brown Fox Jumped Over The Lazy DoTThe Quick Brown Fox Jumped Over TThe QuThe Quick Brown Fox Jumped OveThe Quick Brown Fox Jumped Over The The Quick Brown Fox JumpThe Quick Brown Fox JuThe Quick Brown Fox Jumped OverThe Quick Brown Fox Jumped Over The LThe Quick Brown Fox Jumped OThe Quick Brown FThe Quick Brown Fox Jumped Over The Quick Brown Fox Jumped Over The LaThe Quick Brown FoxThe QuiThe Quick Brown Fox JumThe QThe Quick Brown Fox The >>
86505: exiting

原子写作 - 非原子阅读

此代码使用writev()将消息长度和消息写入管道。必要时,它使用两个读取来获取数据,获取长度,然后获取消息。这适用于单个读卡器;对于多个读取器,您必须在读取器之间进行协调,以便一个读取器不访问文件描述符,而另一个读取器读取长度但不读取数据。该代码使用writev()系统调用在单个系统调用中写入长度和数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

static void childish(int fd)
{
    int  nbytes;
    int  pid = getpid();
    while (read(fd, &nbytes, sizeof(nbytes)) == sizeof(nbytes))
    {
        char buffer[1024];
        int  actual;
        if ((actual = read(fd, buffer, nbytes)) != nbytes)
        {
            fprintf(stderr, "%.5d: short read (wanted %d, actual %d)\n", pid, nbytes, actual);
            break;
        }
        printf("%.5d: %.4d <<%.*s>>\n", pid, nbytes, nbytes, buffer);
        fflush(stdout);
    }
    printf("%.5d: exiting\n", pid);
    exit(0);
}

static void parental(int fd)
{
    char message[] = "The Quick Brown Fox Jumped Over The Lazy Dog";
    int nbytes = 0;
    struct iovec req[2];
    req[0].iov_base = &nbytes;
    req[0].iov_len  = sizeof(nbytes);
    req[1].iov_base = message;
    req[1].iov_len  = 0;
    int  pid = getpid();
    for (int i = 0; i < 20; i++)
    {
        do
        {
            nbytes = rand() % (sizeof(message) - 1);
        } while (nbytes == 0);
        req[1].iov_len = nbytes;
        if (writev(fd, req, 2) != (int)(nbytes + sizeof(nbytes)))
            break;
        printf("%.5d: %.4d <<%.*s>>\n", pid, nbytes, nbytes, message);
        fflush(stdout);
    }
    printf("%.5d: exiting\n", pid);
    exit(0);
}

int main(void)
{
    int fd[2];
    pipe(fd);
    pid_t pid = fork();
    if (pid < 0)
        fprintf(stderr, "failed to fork\n");
    else if (pid == 0)
    {
        close(fd[1]);
        childish(fd[0]);
    }
    else
    {
        close(fd[0]);
        parental(fd[1]);
    }
    return EXIT_FAILURE;  // Failed to fork
}

示例输出

86798: 0043 <<The Quick Brown Fox Jumped Over The Lazy Do>>
86798: 0001 <<T>>
86798: 0033 <<The Quick Brown Fox Jumped Over T>>
86798: 0006 <<The Qu>>
86798: 0030 <<The Quick Brown Fox Jumped Ove>>
86798: 0036 <<The Quick Brown Fox Jumped Over The >>
86798: 0024 <<The Quick Brown Fox Jump>>
86798: 0022 <<The Quick Brown Fox Ju>>
86798: 0031 <<The Quick Brown Fox Jumped Over>>
86798: 0037 <<The Quick Brown Fox Jumped Over The L>>
86798: 0028 <<The Quick Brown Fox Jumped O>>
86798: 0017 <<The Quick Brown F>>
86798: 0032 <<The Quick Brown Fox Jumped Over >>
86798: 0038 <<The Quick Brown Fox Jumped Over The La>>
86798: 0019 <<The Quick Brown Fox>>
86798: 0007 <<The Qui>>
86798: 0023 <<The Quick Brown Fox Jum>>
86798: 0005 <<The Q>>
86798: 0020 <<The Quick Brown Fox >>
86798: 0004 <<The >>
86798: exiting
86799: 0043 <<The Quick Brown Fox Jumped Over The Lazy Do>>
86799: 0001 <<T>>
86799: 0033 <<The Quick Brown Fox Jumped Over T>>
86799: 0006 <<The Qu>>
86799: 0030 <<The Quick Brown Fox Jumped Ove>>
86799: 0036 <<The Quick Brown Fox Jumped Over The >>
86799: 0024 <<The Quick Brown Fox Jump>>
86799: 0022 <<The Quick Brown Fox Ju>>
86799: 0031 <<The Quick Brown Fox Jumped Over>>
86799: 0037 <<The Quick Brown Fox Jumped Over The L>>
86799: 0028 <<The Quick Brown Fox Jumped O>>
86799: 0017 <<The Quick Brown F>>
86799: 0032 <<The Quick Brown Fox Jumped Over >>
86799: 0038 <<The Quick Brown Fox Jumped Over The La>>
86799: 0019 <<The Quick Brown Fox>>
86799: 0007 <<The Qui>>
86799: 0023 <<The Quick Brown Fox Jum>>
86799: 0005 <<The Q>>
86799: 0020 <<The Quick Brown Fox >>
86799: 0004 <<The >>
86799: exiting

我对这个例子中的进程严格连续运行感到失望;因为这是多核机器,所以不是我的预期。当我将循环限制从20更改为2000时,我得到了交叉执行,并且数据在发送和接收方保持同步。

我使用长度为4字节int的值。显然,对于现有数据,使用1字节unsigned char就足够了(哎呀,它甚至可能是signed char,因为字符串只有44个字符长。)

请注意,我没有播种rand()生成器,因此每次运行时输出都是确定性的,而不是进程ID。

另请注意,我在阅读POSIX规范时并不完全确定writev()段可以保证在管道上被视为一个单元。如果不是这种情况,那么您需要在parental()代码中制作一个缓冲区,其中包含长度,后跟相关数据量,然后回到普通write()调用。这一点都不难:

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

static void childish(int fd)
{
    char nbytes;
    int  pid = getpid();
    while (read(fd, &nbytes, sizeof(nbytes)) == sizeof(nbytes))
    {
        char buffer[1024];
        int  actual;
        if ((actual = read(fd, buffer, nbytes)) != nbytes)
        {
            fprintf(stderr, "%.5d: short read (wanted %d, actual %d)\n", pid, nbytes, actual);
            break;
        }
        printf("%.5d: %.4d <<%.*s>>\n", pid, nbytes, nbytes, buffer);
        fflush(stdout);
    }
    printf("%.5d: exiting\n", pid);
    exit(0);
}

static void parental(int fd)
{
    char message[] = "\000The Quick Brown Fox Jumped Over The Lazy Dog";
    int  pid = getpid();
    for (int i = 0; i < 20; i++)
    {
        int nbytes;
        do
        {
            nbytes = rand() % (sizeof(message) - 1);
        } while (nbytes == 0);
        message[0] = nbytes;
        if (write(fd, message, nbytes + 1) != (nbytes + 1))
            break;
        printf("%.5d: %.4d <<%.*s>>\n", pid, nbytes, nbytes, message+1);
        fflush(stdout);
    }
    printf("%.5d: exiting\n", pid);
    exit(0);
}

int main(void)
{
    int fd[2];
    pipe(fd);
    pid_t pid = fork();
    if (pid < 0)
        fprintf(stderr, "failed to fork\n");
    else if (pid == 0)
    {
        close(fd[1]);
        childish(fd[0]);
    }
    else
    {
        close(fd[0]);
        parental(fd[1]);
    }
    return EXIT_FAILURE;  // Failed to fork
}

添加线程到编写代码

创建线程来完成写作并不是那么难。代码仍然使用rand(),但rand()不保证是线程安全的,因此它可能不如您所愿。另一方面,这段代码只是使用rand()生成可变大小的消息;它表现完美并不重要。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void childish(int fd)
{
    char nbytes;
    int  pid = getpid();
    while (read(fd, &nbytes, sizeof(nbytes)) == sizeof(nbytes))
    {
        char buffer[1024];
        int  actual;
        if ((actual = read(fd, buffer, nbytes)) != nbytes)
        {
            fprintf(stderr, "%.5d: short read (wanted %d, actual %d)\n", pid, nbytes, actual);
            break;
        }
        printf("%.5d: %.4d R <<%.*s>>\n", pid, nbytes, nbytes, buffer);
        fflush(stdout);
    }
    printf("%.5d: exiting\n", pid);
    exit(0);
}

static void *p_thread(void *data)
{
    int fd = *(int *)data;
    char message[] = "\000The Quick Brown Fox Jumped Over The Lazy Dog";
    int  pid = getpid();
    for (int i = 0; i < 20; i++)
    {
        int nbytes;
        do
        {
            nbytes = rand() % (sizeof(message) - 1);
        } while (nbytes == 0);
        message[0] = nbytes;
        if (write(fd, message, nbytes + 1) != (nbytes + 1))
            break;
        printf("%.5d: %.4d W <<%.*s>>\n", pid, nbytes, nbytes, message+1);
        fflush(stdout);
    }
    printf("%.5d: thread exiting\n", pid);
    return(0);
}

static void parental(int fd)
{
    enum { NUM_THREADS = 3 };
    pthread_t thr[NUM_THREADS];
    int  pid = getpid();
    for (int i = 0; i < NUM_THREADS; i++)
    {
        if (pthread_create(&thr[i], 0, p_thread, (void *)&fd) != 0)
        {
            fprintf(stderr, "%.5d: failed to create thread number %d\n", pid, i);
            exit(EXIT_FAILURE);
        }
    }
    for (int i = 0; i < NUM_THREADS; i++)
    {
        if (pthread_join(thr[i], 0) != 0)
        {
            fprintf(stderr, "%.5d: failed to join thread number %d\n", pid, i);
            exit(EXIT_FAILURE);
        }
    }
    printf("%.5d: master thread exiting\n", pid);
    exit(EXIT_SUCCESS);
}

int main(void)
{
    int fd[2];
    pipe(fd);
    pid_t pid = fork();
    if (pid < 0)
        fprintf(stderr, "failed to fork\n");
    else if (pid == 0)
    {
        close(fd[1]);
        childish(fd[0]);
    }
    else
    {
        close(fd[0]);
        parental(fd[1]);
    }
    return EXIT_FAILURE;  // Failed to fork
}

请注意,p_thread()(线程函数)几乎是先前parental函数的副本,但新的parental()函数协调三个线程的创建和终止。 childish()main()中的代码根本不需要更改(尽管我将R添加到childish()中的打印件以匹配代码中的Wp_thread())。