c recv () читать до появления новой строки - PullRequest
10 голосов
/ 23 мая 2011

Я работаю над написанием IRC-бота на C и наткнулся на загадку.

В своей основной функции я создаю свой сокет и подключаю, все эти приятные вещи.Затем у меня есть (почти) бесконечный цикл для чтения того, что отправляется обратно с сервера.Затем я передаю прочитанное вспомогательной функции processLine(char *line) - проблема в том, что следующий код читает до тех пор, пока мой буфер не заполнится - я хочу, чтобы он считывал только текст до новой строки (\ n) или возврата каретки (\ r) происходит (таким образом заканчивая эту строку)

   while (buffer[0] && buffer[1]) {
        for (i=0;i<BUFSIZE;i++) buffer[i]='\0';
        if (recv(sock, buffer, BUFSIZE, 0) == SOCKET_ERROR)
            processError();

        processLine(buffer);
    }

В конечном итоге происходит то, что многие строки заклинивают все вместе, и я не могу правильно обработать строки, когда это происходит.

Есливы не знакомы с протоколами IRC, краткая сводка состоит в том, что при отправке сообщения оно часто выглядит следующим образом: :YourNickName!YourIdent@YourHostName PRIVMSG #someChannel :The rest on from here is the message sent..., а уведомление о входе в систему, например, выглядит примерно так: :the.hostname.of.the.server ### bla some text bla с ###код (?), используемый для обработки, т. е. 372 - это показатель того, что следующий текст является частью «Послания дня».

Когда все это собрано вместе, я не могу прочитать, какое число для чегострока, потому что я не могу найти, где начинается или заканчивается линия!

Я был бы очень признателен за помощь в этом!

PS: Это компилируется / запускается в Linux, но я в конечном итогехочу перенести его на windows,поэтому я делаю как можно больше из мультиплатформы.

PSS: Вот мой код processLine ():

void processLine(const char *line) {
    char *buffer, *words[MAX_WORDS], *aPtr;
    char response[100];
    int count = 0, i;
    buffer = strdup(line);

    printf("BLA %s", line);

    while((aPtr = strsep(&buffer, " ")) && count < MAX_WORDS)
        words[count++] = aPtr;
        printf("DEBUG %s\n", words[1]);
    if (strcmp(words[0], "PING") == 0) {
        strcpy(response, "PONG ");
        strcat(response, words[1]);
        sendLine(NULL, response); /* This is a custom function, basically it's a send ALL function */
    } else if (strcmp(words[1], "376") == 0) { /* We got logged in, send login responses (i.e. channel joins) */
        sendLine(NULL, "JOIN #cbot");
    }
}

Ответы [ 2 ]

11 голосов
/ 23 мая 2011

Обычный способ справиться с этим - recv в постоянный буфер в вашем приложении, затем вытащить одну строку и обработать ее.Позже вы можете обработать оставшиеся строки в буфере перед повторным вызовом recv.Имейте в виду, что последняя строка в буфере может быть получена только частично;Вы должны разобраться с этим делом, введя recv, чтобы завершить строку.

Вот пример (полностью непроверенный! также ищет \n, а не \r\n):

#define BUFFER_SIZE 1024
char inbuf[BUFFER_SIZE];
size_t inbuf_used = 0;

/* Final \n is replaced with \0 before calling process_line */
void process_line(char *lineptr);
void input_pump(int fd) {
  size_t inbuf_remain = sizeof(inbuf) - inbuf_used;
  if (inbuf_remain == 0) {
    fprintf(stderr, "Line exceeded buffer length!\n");
    abort();
  }

  ssize_t rv = recv(fd, (void*)&inbuf[inbuf_used], inbuf_remain, MSG_DONTWAIT);
  if (rv == 0) {
    fprintf(stderr, "Connection closed.\n");
    abort();
  }
  if (rv < 0 && errno == EAGAIN) {
    /* no data for now, call back when the socket is readable */
    return;
  }
  if (rv < 0) {
    perror("Connection error");
    abort();
  }
  inbuf_used += rv;

  /* Scan for newlines in the line buffer; we're careful here to deal with embedded \0s
   * an evil server may send, as well as only processing lines that are complete.
   */
  char *line_start = inbuf;
  char *line_end;
  while ( (line_end = (char*)memchr((void*)line_start, '\n', inbuf_used - (line_start - inbuf))))
  {
    *line_end = 0;
    process_line(line_start);
    line_start = line_end + 1;
  }
  /* Shift buffer down so the unprocessed data is at the start */
  inbuf_used -= (line_start - inbuf);
  memmove(innbuf, line_start, inbuf_used);
}
7 голосов
/ 23 мая 2011

TCP не предлагает никакой последовательности такого рода. Как уже сказал @bdonlan, вы должны реализовать что-то вроде:

  • Непрерывно recv из сокета в буфер
  • На каждом recv проверьте, содержат ли полученные байты \n
  • Если \n использовать все до этой точки из буфера (и очистить его)

У меня нет хорошего чувства по этому поводу (я где-то читал, что вы не должны смешивать низкоуровневый ввод-вывод с stdio I / O), но вы можете использовать fdopen.

Все, что вам нужно сделать, это

  • используйте fdopen(3), чтобы связать ваше гнездо с FILE *
  • используйте setvbuf, чтобы сообщить stdio, что вы хотите использовать линейную буферизацию (_IOLBF), а не блочную буферизацию по умолчанию.

На этом этапе вы должны были эффективно переместить работу из рук в stdio. Тогда вы можете продолжить использовать fgets и т.п. на FILE *.

...