Как мне обработать поток данных внутри приложения на C? - PullRequest
4 голосов
/ 13 октября 2010

Я извлекаю данные из потока bzip2 в приложении C. Когда порции данных выходят из декомпрессора, они могут быть записаны в stdout:

fwrite(buffer, 1, length, stdout);

Это прекрасно работает. Я получаю все данные, когда они отправляются на stdout.

Вместо записи в stdout я хотел бы обработать вывод этого оператора внутри себя в виде однострочных кусков: строки, заканчивающейся символом новой строки \n.

Записывать ли вывод потока декомпрессора в другой буфер, по одному символу за раз, пока я не нажму на новую строку, а затем вызвать функцию обработки для каждой строки? Это медленно и есть более разумный подход? Спасибо за ваш совет.

EDIT

Спасибо за ваши предложения. Я закончил тем, что создал пару буферов, которые сохраняют остаток («заглушку» в конце буфера вывода) в начале буфера короткой строки, каждый раз, когда я прохожу через данные выходного буфера.

Я перебираю выходной буфер символ за символом и обрабатываю значение данных новой строки за раз. Остаток без новой строки выделяется и присваивается, а также копируется в строковый буфер следующего потока. Похоже, что realloc дешевле, чем повторные malloc-free заявления.

Вот код, который я придумал:

char bzBuf[BZBUFMAXLEN];
BZFILE *bzFp;
int bzError, bzNBuf;
char bzLineBuf[BZLINEBUFMAXLEN];
char *bzBufRemainder = NULL;
int bzBufPosition, bzLineBufPosition;

bzFp = BZ2_bzReadOpen(&bzError, *fp, 0, 0, NULL, 0); /* http://www.bzip.org/1.0.5/bzip2-manual-1.0.5.html#bzcompress-init */ 

if (bzError != BZ_OK) {
    BZ2_bzReadClose(&bzError, bzFp);   
    fprintf(stderr, "\n\t[gchr2] - Error: Bzip2 data could not be retrieved\n\n");
    return -1;          
}

bzError = BZ_OK;
bzLineBufPosition = 0;
while (bzError == BZ_OK) {

    bzNBuf = BZ2_bzRead(&bzError, bzFp, bzBuf, sizeof(bzBuf));

    if (bzError == BZ_OK || bzError == BZ_STREAM_END) {
        if (bzBufRemainder != NULL) {
            /* fprintf(stderr, "copying bzBufRemainder to bzLineBuf...\n"); */
            strncpy(bzLineBuf, bzBufRemainder, strlen(bzBufRemainder)); /* leave out \0 */
            bzLineBufPosition = strlen(bzBufRemainder);
        }

        for (bzBufPosition = 0; bzBufPosition < bzNBuf; bzBufPosition++) {
            bzLineBuf[bzLineBufPosition++] = bzBuf[bzBufPosition];
            if (bzBuf[bzBufPosition] == '\n') {
                bzLineBuf[bzLineBufPosition] = '\0'; /* terminate bzLineBuf */

                /* process the line buffer, e.g. print it out or transform it, etc. */
                fprintf(stdout, "%s", bzLineBuf);

                bzLineBufPosition = 0; /* reset line buffer position */
            }
            else if (bzBufPosition == (bzNBuf - 1)) {
                bzLineBuf[bzLineBufPosition] = '\0';
                if (bzBufRemainder != NULL)
                    bzBufRemainder = (char *)realloc(bzBufRemainder, bzLineBufPosition);
                else
                    bzBufRemainder = (char *)malloc(bzLineBufPosition);
                strncpy(bzBufRemainder, bzLineBuf, bzLineBufPosition);
            }
        }
    }
}

if (bzError != BZ_STREAM_END) {
    BZ2_bzReadClose(&bzError, bzFp);
    fprintf(stderr, "\n\t[gchr2] - Error: Bzip2 data could not be uncompressed\n\n");
    return -1;  
} else {   
    BZ2_bzReadGetUnused(&bzError, bzFp, 0, 0);
    BZ2_bzReadClose(&bzError, bzFp);
}

free(bzBufRemainder);
bzBufRemainder = NULL;

Я действительно ценю помощь каждого. Это работает хорошо.

Ответы [ 4 ]

2 голосов
/ 24 сентября 2015

Я также работал с обработкой данных bzip2 на строку и обнаружил, что чтение одного байта за раз было слишком медленным.Это работало лучше для меня:

#include <stdio.h>
#include <stdlib.h>
#include <bzlib.h>

/* gcc -o bz bz.c -lbz2 */

#define CHUNK 128

struct bzdata {
  FILE *fp;
  BZFILE *bzf;
  int bzeof, bzlen, bzpos;
  char bzbuf[4096];
};

static int bz2_open(struct bzdata *bz, char *file);
static void bz2_close(struct bzdata *bz);
static int bz2_read_line(struct bzdata *bz, char **line, int *li);
static int bz2_buf(struct bzdata *bz, char **line, int *li, int *ll);


static int
bz2_buf(struct bzdata *bz, char **line, int *li, int *ll)
{
  int done = 0;

  for (; bz->bzpos < bz->bzlen && done == 0; bz->bzpos++) {
    if (*ll + 1 >= *li) {
      *li += CHUNK;
      *line = realloc(*line, (*li + 1) * sizeof(*(*line)));
    }
    if ( ((*line)[(*ll)++] = bz->bzbuf[bz->bzpos]) == '\n') {
      done = 1;
    }
  }

  if (bz->bzpos == bz->bzlen) {
    bz->bzpos = bz->bzlen  = 0;
  }

  (*line)[*ll] = '\0';

  return done;
}

static int
bz2_read_line(struct bzdata *bz, char **line, int *li)
{
  int bzerr = BZ_OK, done = 0, ll = 0;

  if (bz->bzpos) {
    done = bz2_buf(bz, line, li, &ll);
  }

  while (done == 0 && bz->bzeof == 0) {
    bz->bzlen = BZ2_bzRead(&bzerr, bz->bzf, bz->bzbuf, sizeof(bz->bzbuf));

    if (bzerr == BZ_OK || bzerr == BZ_STREAM_END) {
      bz->bzpos = 0;

      if (bzerr == BZ_STREAM_END) {
        bz->bzeof = 1;
      }
      done = bz2_buf(bz, line, li, &ll);
    } else { 
      done = -1;
    }
  }

  /* Handle last lines that don't have a line feed */
  if (done == 0 && ll > 0 && bz->bzeof) {
    done = 1;
  }

  return done;
}

static int
bz2_open(struct bzdata *bz, char *file)
{
  int bzerr = BZ_OK;

  if ( (bz->fp = fopen(file, "rb")) &&
       (bz->bzf = BZ2_bzReadOpen(&bzerr, bz->fp, 0, 0, NULL, 0)) &&
       bzerr == BZ_OK) {
    return 1;
  }

  return 0;
}

static void
bz2_close(struct bzdata *bz)
{
  int bzerr;

  if (bz->bzf) {
    BZ2_bzReadClose(&bzerr, bz->bzf);
    bz->bzf = NULL;
  }

  if (bz->fp) {
    fclose(bz->fp);
    bz->fp = NULL;
  }
  bz->bzpos = bz->bzlen = bz->bzeof = 0;
}

int main(int argc, char *argv[]) {
  struct bzdata *bz = NULL;
  int i, lc, li = 0;
  char *line = NULL;

  if (argc < 2) {
    return fprintf(stderr, "usage: %s file [file ...]\n", argv[0]);
  }  

  if ( (bz = calloc(1, sizeof(*bz))) ) {
    for (i = 1; i < argc; i++) {
      if (bz2_open(bz, argv[i])) {
        for (lc = 0; bz2_read_line(bz, &line, &li) > 0; lc++) {
          /* Process line here */
        }
        printf("%s: lines=%d\n", argv[i], lc);
      }
      bz2_close(bz);
    }

    free(bz);
  }

  if (line) {
    free(line);
  }

  return 0;
}
2 голосов
/ 13 октября 2010

Я не думаю, что есть более разумный подход (кроме поиска библиотеки автоматов, которая уже делает это для вас). Будьте осторожны с выделением правильного размера для буфера «последней строки»: если он не может обработать произвольную длину, а входные данные поступают из чего-то доступного для третьих сторон, это становится угрозой безопасности.

1 голос
/ 13 октября 2010

Это было бы легко сделать, используя std::string в C ++, но в C требуется некоторый код, если вы хотите сделать это эффективно (если вы не используете динамическую библиотеку строк).xmalloc и xrealloc обрабатывают ошибки внутренне. Не забудьте free возвращенную строку.)

Это почти на порядок медленнее bzcat:

lars@zygmunt:/tmp$ wc foo
 1193  5841 42868 foo
lars@zygmunt:/tmp$ bzip2 foo
lars@zygmunt:/tmp$ time bzcat foo.bz2 > /dev/null

real    0m0.010s
user    0m0.008s
sys     0m0.000s
lars@zygmunt:/tmp$ time ./a.out < foo.bz2 > /dev/null

real    0m0.093s
user    0m0.044s
sys     0m0.020s

Решите для себя, приемлемо ли это.

0 голосов
/ 13 октября 2010

Я думаю, вам следует копировать куски символов в другой буфер, пока последний записанный вами блок не будет содержать символ новой строки.Затем вы можете работать со всей строкой.

Вы можете сохранить оставшуюся часть буфера (после '\n') во временном файле и затем создать из него новую строку.

...