Реализация FIFO - PullRequest
       13

Реализация FIFO

5 голосов
/ 20 мая 2010

Рассмотрим следующий код:

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?
Потому что, если я, например, прочитал 10 байтов в считывателе, а foo и bar вместе меньше 10 байтов, я получу их оба в одной переменной, и я не хочу.
В идеале у меня была бы одна функция чтения для каждой переменной, но опять же я не знаю заранее, сколько байтов будет иметь данные.
Я думал о добавлении еще одной инструкции записи в writer.c между записью для foo и bar с разделителем, и тогда у меня не возникло бы проблем с декодированием ее из reader.c. Это способ пойти по этому поводу?

Спасибо.

Ответы [ 5 ]

7 голосов
/ 20 мая 2010

Во многих других ответах упоминается использование какого-либо протокола для ваших данных, и я считаю, что это правильный подход. Этот протокол может быть настолько простым или сложным, насколько это необходимо. Я привел несколько примеров, которые могут оказаться полезными 1 .


В простом случае вы можете иметь только байт длины, за которым следуют байты данных (то есть строка C).

+--------------+
| length byte  |
+--------------+
| data byte(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));

Reader:

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.

+----------------+
|  length byte   |
+----------------+
| data type byte |
+----------------+
|  data byte(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));

Reader:

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 голосов
/ 20 мая 2010

Разделитель - это один из способов сделать это, и он будет работать нормально, если вы знаете порядок ваших данных и используете разделитель только как разделитель, а не как часть ваших данных.

Другой способ состоит в том, чтобы перед каждой записью в канал было задано количество байтов фиксированной ширины. Таким образом, вы будете знать, сколько данных собирается поступить по конвейеру. Используйте фиксированную ширину, чтобы точно знать, какой длины будет поле ширины, чтобы вы знали, когда начинать и останавливать чтение каждого куска данных.

1 голос
/ 20 мая 2010

Вам нужно будет определить какой-нибудь проводной протокол или формат сериализации / десериализации, чтобы ваш читатель знал, как интерпретировать данные, которые он читает из fifo. Использование разделителя - это самый простой способ добиться этого, но вы столкнетесь с проблемами, если ваш разделитель когда-либо появится как часть вывода данных вашего устройства записи.

Чуть дальше по шкале сложности ваш протокол может определять как разделитель, так и способ указания длины каждой «части» или «сообщения» данных, которые вы отправляете.

Наконец, эта проблема более тщательно решается путем написания сериализованных сообщений, которые ваш писатель затем десериализует после получения. Вам может быть интересно использовать что-то вроде Protocol Buffers или Thrift для достижения этой цели (с дополнительным бонусом, который вы можете использовать для чтения или записи на нескольких различных языках программирования без изменения протокол).

1 голос
/ 20 мая 2010

Чтобы немного обобщить ответ WhirlWind, вы должны установить протокол НЕКОТОРОГО разнообразия. Как вы указываете, должен быть порядок того, что вы отправляете, иначе вы не знаете сверху вниз.

Оба предложения WhirlWind будут работать. Вы также можете зайти так далеко, чтобы внедрить пользовательский (или стандартный) протокол поверх канала или FIFO, чтобы сделать перенос вашего кода в более распределенную среду с разрозненными системами и более легкой задачей позже. Суть проблемы, однако, в том, что вам нужно установить ПРАВИЛА для общения, прежде чем вы сможете на самом деле общаться.

1 голос
/ 20 мая 2010

Разделитель действительно является одним из способов сделать это - и достаточно удобно, строки C поставляются с таким разделителем - nul-terminator в конце строки.

Если вы измените свои write() вызовы так, чтобы они также записывали нулевой терминатор (обратите внимание, что sizeof(char) определен как 1, так что его можно опустить):

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

Затем вы можете выделить строки после того, как прочитали их (вам все равно нужно будет прочитать их в один буфер, а затем разбить их на части, если только вы не прочитаете их символ за раз).

...