5

考虑以下代码:

作家.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));

读者.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解码它就没有问题了。这是解决问题的方法吗?

谢谢。

4

5 回答 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 - 此代码是从内存中编写的,未经测试。

于 2010-05-20T06:46:56.443 回答
6

分隔符是解决此问题的一种方法,只要您知道数据的顺序,并且将分隔符仅用作分隔符,而从不用作数据的一部分,它就可以正常工作。

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

于 2010-05-20T01:50:44.827 回答
1

分隔符确实是执行此操作的一种方法——而且很方便,C 字符串带有这样的分隔符——字符串末尾的 nul 终止符。

如果你改变你的write()调用,让他们也写出 nul-terminator(注意它sizeof(char)被定义为 1,所以它可以被省略):

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

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

于 2010-05-20T01:55:50.190 回答
1

为了稍微概括 WhirlWind 的答案,你必须建立一个各种各样的协议。正如您所指出的,您发送的内容必须有顺序,否则您不知道自上而下。

WhirlWind 的两个建议都会奏效。您还可以在管道或 FIFO 之上实现自定义(或标准)协议,以便将您的代码移植到具有不同系统和更轻松任务的分布式环境中。然而,问题的症结在于,您必须先设定沟通规则,然后才能真正进行沟通。

于 2010-05-20T01:58:41.700 回答
1

您必须定义某种有线协议或序列化/反序列化格式,以便您的读者知道如何解释它从 fifo 读取的数据。使用分隔符是解决此问题的最简单方法,但如果分隔符出现在编写器的数据输出中,则会遇到问题。

在复杂度范围内稍远一点,您的协议可能会定义分隔符和指示您发送的每个“片段”或“消息”数据长度的方式。

最后,通过编写序列化消息更彻底地解决了这个问题,您的作者将在收到这些消息后反序列化。您可能有兴趣使用诸如Protocol BuffersThrift之类的东西来实现这一点(额外的好处是您可以用多种不同的编程语言实现您的阅读器或编写器,而无需修改您的协议)。

于 2010-05-20T01:58:48.787 回答