#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
или aread
делает. Стандарт гласит, что ни одна функция не должна устанавливатьerrno
значение 0, но я полагаю, что это означает «успех». Таким образом, даже когдаrecv
происходит сбой, фактический fgets выполняется успешно.4. О, отлично. Это просто выбросило весь мой код обработки ошибок прямо из окна! В любом случае, это было полезно независимо. Спасибо!