考虑下面的代码:
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会有多less字节,我怎么知道从reader.c中读取多less字节?
因为如果我在阅读器中读取10个字节,并且foo和bar在一起less于10个字节,我将把它们放在同一个variables中,而我不想要它们。
理想情况下,我将有一个读取每个variables的函数,但我不知道数据有多less字节。
我想在writer.c之间添加另一个write指令,在foo和bar之间用分隔符写,然后从reader.c中解码就没有问题。 这是要走的路吗?
谢谢。
分隔符是一种可行的方法,只要知道数据的顺序,并且只使用分隔符作为分隔符,而不是数据的一部分,就可以正常工作。
另一种方法是在固定宽度内每次写入管道的数量都要跟随的字节数。 因此,你会知道有多少数据即将下降。 使用一个固定的宽度,所以你知道宽度字段将是多久,所以你知道什么时候开始和停止读取每个数据块。
其他许多答案都提到使用某种协议来处理您的数据,我相信这是正确的做法。 这个协议可以根据需要简单或复杂。 我提供了几个例子,你可能会觉得有用1 。
在一个简单的情况下,你可能只有一个字节的长度,然后是数据字节(即C字符串)。
+ -------------- + | 长度字节| + -------------- + | 数据字节(s)| + -------------- +
作家:
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字符串的数据字节。
+ ---------------- + | 长度字节| + ---------------- + | 数据类型字节| + ---------------- + | 数据字节(s)| + ---------------- +
通用标题:
#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 – 这段代码是从内存写入的,没有经过测试。
分隔符的确是这样做的一种方式 – 并且方便的是,C字符串带有这样一个分隔符 – 字符串末尾的nul结束符。
如果你改变你的write()
调用,这样他们也写出了nul结束符(注意sizeof(char)
被定义为1,所以可以省略):
write(fd, foo, strlen(foo) + 1); write(fd, bar, strlen(bar) + 1);
然后,你可以在读入字符串之后挑选字符串(你仍然需要将它们读入一个缓冲区,然后将它们分开,除非一次读取一个字符)。
为了概括WhirlWind的答案,你必须建立一些协议。 如你所指出的那样,你必须有秩序地发送你的内容,否则你不知道自己的底线。
WhirlWind的两个建议都可以使用。 您还可以在管道或FIFO的顶部实施自定义(或标准)协议,以便将代码移植到具有不同系统的更分布式环境中,以后再轻松完成任务。 问题的症结在于,在你能够实际沟通之前,你必须设置RULES进行沟通。
您必须定义某种有线协议或序列化/反序列化格式,以便读者知道如何解释从fifo读取的数据。 使用分隔符是解决这个问题的最简单的方法,但是如果您的分隔符作为作者的数据输出的一部分出现,您将遇到问题。
在复杂度上稍微远一点,你的协议可能会定义一个分隔符和一个方法来表示你发送的数据的每一个“块”或“消息”的长度。
最后,通过编写序列化的消息,您的作者将在接收后将反序列化,这个问题会得到更彻底的解决。 你可能有兴趣使用像Protocol Buffers或Thrift这样的东西来实现这个目标(还有额外的好处,你可以用不同的编程语言来实现你的读写器,而不用修改你的协议)。