FIFO的实现

5

请看下面的代码:

writer.c

mkfifo("/tmp/myfifo", 0660);

int fd = open("/tmp/myfifo", O_WRONLY);

char *foo, *bar;

...

write(fd, foo, strlen(foo)*sizeof(char));
write(fd, bar, strlen(bar)*sizeof(char));

reader.c

int fd = open("/tmp/myfifo", O_RDONLY);

char buf[100];
read(fd, buf, ??);

我的问题是:

由于不知道foo和bar将有多少字节,我如何知道从reader.c中读取多少字节?
因为如果我在reader中读取了10个字节,而foo和bar加起来少于10个字节,那么它们就会在同一个变量中,这是我不想要的。
理想情况下,我会为每个变量编写一个读取函数,但是我不知道数据将有多少字节。
我考虑在writer.c中添加另一个写指令,在foo和bar的写入之间添加一个分隔符,然后我就可以轻松地从reader.c中解码它。这是正确的方法吗?

谢谢。


注意:ANSI C保证sizeof(char)始终等于1。 - Dietrich Epp
5个回答

7
许多其他答案提到使用某种协议来处理数据,我认为这是正确的方法。该协议可以尽可能简单或复杂。我提供了一些你可能会觉得有用的例子1

在一个简单的情况下,你只需要一个长度字节,后面跟着数据字节(即C字符串)。

+--------------+
| 长度字节      |
+--------------+
| 数据字节     |
+--------------+

作者:

uint8_t foo[UCHAR_MAX+1];
uint8_t len;
int fd;

mkfifo("/tmp/myfifo", 0660);
fd = open("/tmp/myfifo", O_WRONLY);

memset(foo, UCHAR_MAX+1, 0);
len = (uint8_t)snprintf((char *)foo, UCHAR_MAX, "Hello World!");

/* The length byte is written first followed by the data. */
write(fd, len, 1);
write(fd, foo, strlen(foo));

读者:

uint8_t buf[UCHAR_MAX+1];
uint8_t len;
int fd;

fd = open("/tmp/myfifo", O_RDONLY);

memset(buf, UCHAR_MAX+1, 0);

/* The length byte is read first followed by a read 
 * for the specified number of data bytes.
 */
read(fd, len, 1);
read(fd, buf, len);

在更复杂的情况下,您可能会有一个长度字节,后跟包含不止一个简单C字符串的数据字节。

+----------------+
|  长度字节       |
+----------------+
| 数据类型字节    |
+----------------+
|  数据字节       |
+----------------+

常见头部:

#define FOO_TYPE 100
#define BAR_TYPE 200

typedef struct {
    uint8_t type;
    uint32_t flags;
    int8_t msg[20];
} __attribute__((aligned, packed)) foo_t;

typedef struct {
    uint8_t type;
    uint16_t flags;
    int32_t value;
} __attribute__((aligned, packed)) bar_t;

作者:

foo_t foo;
unsigned char len;
int fd;

mkfifo("/tmp/myfifo", 0660);
fd = open("/tmp/myfifo", O_WRONLY);

memset(&foo, sizeof(foo), 0);
foo.type = FOO_TYPE;
foo.flags = 0xDEADBEEF;
snprintf(foo.msg, 20-1, "Hello World!");

/* The length byte is written first followed by the data. */
len = sizeof(foo);
write(fd, len, 1);
write(fd, foo, sizeof(foo));

读者:

uint8_t buf[UCHAR_MAX+1];
uint8_t len;
uint16_t type;
union data {
    foo_t * foo;
    bar_t * bar;
}
int fd;

fd = open("/tmp/myfifo", O_RDONLY);

memset(buf, UCHAR_MAX+1, 0);

/* The length byte is read first followed by a read 
 * for the specified number of data bytes.
 */
read(fd, len, 1);
read(fd, buf, len);

/* Retrieve the message type from the beginning of the buffer. */
memcpy(&type, buf, sizeof(type));

/* Process the data depending on the type. */
switch(type) {
    case FOO_TYPE:
        data.foo = (foo_t)buf;
        printf("0x%08X: %s\n", data.foo.flags, data.foo.msg); 
        break;
    case BAR_TYPE:
        data.bar = (bar_t)buf;
        printf("0x%04X: %d\n", data.bar.flags, data.bar.value); 
        break;
    default:
        printf("unrecognized type\n");
}

1 - 这段代码是凭记忆编写的,未经过测试。


6

一种方法是使用分隔符,只要您知道数据的顺序,并且将分隔符仅用作分隔符而不是数据的一部分,那么这种方法就可以正常工作。

另一种方法是在每次写入管道之前,以固定宽度指定要跟随的字节数。 因此,您将知道即将传输多少数据。 使用固定宽度,以便您确切地知道宽度字段的长度,因此您知道何时开始和停止读取每个数据块。


1

分隔符确实是一种方法 - 幸运的是,C字符串带有这样的分隔符 - 字符串末尾的空终止符。

如果您更改write()调用,使其也写出空终止符(请注意,sizeof(char)被定义为1,因此可以省略):

write(fd, foo, strlen(foo) + 1);
write(fd, bar, strlen(bar) + 1);

读入字符串后,您可以将其拆分(除非您一次读取一个字符,否则仍需要将它们读入一个缓冲区然后拆分)。


1
要稍微概括WhirlWind的回答,你必须建立某种协议。发送的内容必须有序,否则就不知道上下文,正如你所指出的那样。
WhirlWind的两个建议都是可行的。您还可以在管道或FIFO上实现自定义(或标准)协议,以便将来更容易地移植代码到分布式环境中的不同系统和任务。然而,关键问题在于在实际通信之前,必须为通信设置规则。

1

你需要定义一种电线协议或序列化/反序列化格式,以便读取器知道如何解释从FIFO中读取的数据。使用分隔符是最简单的方法,但如果分隔符出现在写入器的数据输出中,则会遇到问题。

稍微复杂一些,你的协议可能定义了分隔符和指示发送的每个“片段”或“消息”的长度的方式。

最后,通过编写序列化消息来更彻底地解决此问题,然后您的写入器将在接收后进行反序列化。您可能有兴趣使用类似于Protocol BuffersThrift的东西来实现这一点(额外的好处是您可以在许多不同的编程语言中实现您的读取器或写入器而无需修改您的协议)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接