UNIX / Linux IPC: чтение из канала. Как узнать длину данных во время выполнения? - PullRequest
4 голосов
/ 20 июля 2009

У меня есть дочерний процесс, который генерирует некоторый вывод переменной длины, а затем отправляет его родителю, используя полудуплексный канал. В родителе, как я могу использовать функцию read ()? Поскольку данные могут быть разной длины каждый раз, как я могу во время выполнения узнать размер данных, чтобы выполнить какую-либо функцию malloc () для буфера? Можно ли использовать функцию fstat () в дескрипторе файла канала?

Я знаю, что функция read () будет считывать указанное количество байтов, но вернет 0, если достигнут конец файла (не символа EOF) до того, как запрошенные байты будут прочитаны.

Я специально запускаю Ubuntu GNU / Linux с ядром 2.6.27-9.

Все примеры в расширенном программировании Ричарда Стивенса в среде UNIX указали либо длину данных при записи в канал, либо использовали функцию fgets () stdio.h. Поскольку я обеспокоен скоростью, я хочу как можно больше избегать использования stdio.h.

Будет ли это обязательно быстрее с общей памятью?

Спасибо, -Dhruv

Ответы [ 6 ]

5 голосов
/ 20 июля 2009

Поскольку кажется, что вы намереваетесь выполнить одно чтение всех данных из конвейера, я думаю, что следующее поможет вам лучше, чем методы разделителя + кодирования или мини-заголовка, предложенные в других ответах:

Из трубы (7) manpage:

Если все файловые дескрипторы ссылаются на конец записи трубы был закрыто, то попытка чтения (2) из трубы увидим конец файла (чтение (2) вернет 0).

Следующий пример был взят из man-страницы pipe (2) и перевернут так, что дочерний элемент пишет, родительский читает (просто чтобы убедиться). Я также добавил буфер переменного размера. Ребенок будет спать 5 секунд. Задержка гарантирует, что exit () дочернего элемента не может иметь ничего общего с pipeio (родительский файл напечатает полную строку перед выходом дочернего элемента).

#include <sys/wait.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

char *
slurpfd(int fd)
{
    const int bytes_at_a_time = 2;
    char *read_buffer = NULL;
    int buffer_size = 0;
    int buffer_offset = 0;
    int chars_io;
    while (1) {
      if (buffer_offset + bytes_at_a_time > buffer_size) {
        buffer_size = bytes_at_a_time + buffer_size * 2;
        read_buffer = realloc(read_buffer, buffer_size);
        if (!read_buffer) {
          perror("memory");
          exit(EXIT_FAILURE);
        }
      }

      chars_io = read(fd,
                  read_buffer + buffer_offset,
                  bytes_at_a_time);
      if (chars_io <= 0) break;
      buffer_offset += chars_io;
    }

    if (chars_io < 0) {
      perror("read");
      exit(EXIT_FAILURE);
    }

    return read_buffer; /* caller gets to free it */
}

int
main(int argc, char *argv[])
{
  int pipefd[2];
  pid_t cpid;

  assert(argc == 2);

  if (pipe(pipefd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  cpid = fork();
  if (cpid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
  }

  if (cpid == 0) {     /* Child writes argv[1] to pipe */
    close(pipefd[0]);  /* Close unused read end */

    write(pipefd[1], argv[1], strlen(argv[1]) + 1);

    close(pipefd[1]);  /* Reader will see EOF */
    /* sleep before exit to make sure that there
       will be a delay after the parent prints it's
       output */
    sleep(5);
    exit(EXIT_SUCCESS);
  } else {             /* Parent reads from pipe */
    close(pipefd[1]);  /* Close unused write end */

    puts(slurpfd(pipefd[0]));

    close(pipefd[0]);
    wait(NULL);        /* Wait for child */
    _exit(EXIT_SUCCESS);
  }
}

Из вашего комментария теперь я вижу, что вы можете читать данные по мере их поступления, обновлять пользовательский интерфейс или что-то еще, чтобы отражать состояние вашей системы. Для этого откройте канал в неблокирующем (O_NONBLOCK) режиме. Прочитайте многократно все, что доступно, пока -1 не вернется и не ошибется == EAGAIN, и выполните анализ. Повторите unil read возвращает 0, что указывает на то, что дочерний элемент закрыл канал.

Чтобы использовать буфер в памяти для функций File *, вы можете использовать fmemopen () в библиотеке GNU C.

2 голосов
/ 20 июля 2009

Вы не можете получить какую-либо информацию о размере из трубы, так как нет размера.

Вам нужно использовать либо определенный размер, либо разделитель.

Другими словами, в дочернем элементе выведите размер предстоящего вывода в виде целого числа, а затем запишите фактический вывод; в родительском элементе вы читаете размер (это int, поэтому он всегда одинакового размера), а затем читаете столько байтов.

Или: определите конечный символ и, пока не увидите, предположите, что вам нужно продолжать читать. Однако для этого может потребоваться какой-то механизм экранирования / кодирования, и, вероятно, он будет не таким быстрым. Я думаю, что это в основном то, что делает Fgets.

2 голосов
/ 20 июля 2009

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

Общая память будет быстрее, поскольку она избегает копий и может избежать некоторых системных вызовов, но протоколы блокировки, необходимые для передачи данных через shmem, более сложны и подвержены ошибкам, поэтому обычно лучше избегать разделяемой памяти, если она вам абсолютно не нужна. Кроме того, в совместно используемой памяти вы должны установить фиксированный максимальный размер для данных, которые будут передаваться при выделении буфера.

1 голос
/ 20 июля 2009

Почему бы не записать длину в трубу в виде (скажем) первых 'n' байтов? Затем на другом конце вы можете прочитать эти байты, определить длину, а затем прочитать это количество байтов (т. Е. У вас очень простой протокол)

0 голосов
/ 20 июля 2009

Вы можете попробовать использовать очереди сообщений IPC, если ваши сообщения не слишком большие.

0 голосов
/ 20 июля 2009

Другие постеры верны: у вас должен быть способ указать длину пакетов самостоятельно. Один конкретный, практичный способ сделать это - netstrings . Его просто создавать и анализировать, и он поддерживается некоторыми распространенными средами, такими как Twisted .

...