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バイト未満である場合、両方を同じ変数に入れ、必要ないためです。
理想的には、変数ごとに1つの読み取り関数がありますが、データのバイト数が事前にわかりません。
fooの書き込みと区切り文字のあるbarの間にwriter.cに別の書き込み命令を追加することを考えました。そうすれば、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

セパレーターはそのための1つの方法であり、データの順序を知っている限り、これは正常に機能します。セパレーターは、データの一部としてではなく、セパレーターとしてのみ使用します。

もう1つの方法は、パイプへの各書き込みの前に、固定幅で後続のバイト数を指定することです。したがって、パイプを通過しようとしているデータの量がわかります。固定幅を使用すると、幅フィールドの長さが正確にわかり、データの各チャンクの読み取りを開始するタイミングと停止するタイミングの両方がわかります。

于 2010-05-20T01:50:44.827 に答える
1

セパレータは確かにこれを行う1つの方法です-そして便利なことに、C文字列にはそのようなセパレータが付属しています-文字列の最後にヌルターミネータがあります。

write()nul-terminatorも書き出すように呼び出しを変更した場合( sizeof(char)1と定義されているため、省略できます):

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

次に、文字列を読み込んだ後、文字列を分解できます(一度に文字を読み取らない限り、文字列を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 に答える