c recv () считывает до появления новой строки

#c #sockets #recv

#c #сокеты #recv

Вопрос:

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

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

    while (buffer[0] amp;amp; buffer[1]) {
        for (i=0;i<BUFSIZE;i  ) buffer[i]='';
        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, поэтому я делаю из него столько, сколько могу, мультиплатформенным.

P.S.S.: Вот мой код 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(amp;buffer, " ")) amp;amp; count < MAX_WORDS)
        words[count  ] = aPtr;
        printf("DEBUG %sn", 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");
    }
}
  

Ответ №1:

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

Вот пример (совершенно непроверенный! также ищет n , не rn ):

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

/* Final n is replaced with  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*)amp;inbuf[inbuf_used], inbuf_remain, MSG_DONTWAIT);
  if (rv == 0) {
    fprintf(stderr, "Connection closed.n");
    abort();
  }
  if (rv < 0 amp;amp; 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 s
   * 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);
}
  

Комментарии:

1. кажется достаточно простым. Однако, как бы мне повторно ввести recv ()? Передал бы я указатель на символ в конец частично прочитанного текста, т. Е. если recv () прочитал только 5 из 10 символов, передал бы указатель на 6-ю позицию вместо этого?

2. @FurryHead: Добавлен (непроверенный) пример

3. Ого. Я давно отказался от этого проекта, чувствуя, что большая часть этого происходит прямо у меня над головой (что, собственно, и было). Теперь я, наконец, возвращаюсь к чрезвычайно похожему проекту (снова irc-бот, но немного другой), и я прочитал это, даже не осознавая, что это была моя тема. Последние 2 дня я бился головой о стол, пытаясь реализовать это (почти в точности то, что вы писали), но, как ни странно, в итоге из случайных позиций в строке был удален только один символ. Странно. В любом случае, просто хотел еще раз поблагодарить вас! Это очень помогло!

4. отлично работает, но (inbuf - line_start); должно быть (line_start - inbuf);

Ответ №2:

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

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

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

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

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

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

Комментарии:

1. Отличная идея, я попробовал это, и это прекрасно работает. Однако у меня есть два вопроса по этому поводу: как мне проверить наличие ошибок в Windows? Обычно я бы использовал WSAGetLastError(), поскольку сокеты Windows используют это, а не errno… И будут ли fdopen() / setvbuf () работать и в Windows?

2. (обновление, когда я пытаюсь обработать errno в Linux с помощью этого, он выдает мне код ошибки 0 — я пока не знаю, чему это соответствует)

3. @FurryHead setvbuf является стандартным; в Windows есть _fdopen . Что касается errno части, при использовании stdio проверки на наличие ошибок с помощью ferror , feof . Очевидно, что это представляет меньше деталей, чем recv или a read делает. Стандарт гласит, что ни одна функция не должна устанавливать errno значение 0, но я полагаю, что это означает «успех». Таким образом, даже когда recv происходит сбой, фактический fgets выполняется успешно.

4. О, отлично. Это просто выбросило весь мой код обработки ошибок прямо из окна! В любом случае, это было полезно независимо. Спасибо!