Samstag, Januar 18, 2020

c recv() liest bis zum newline-Auftritt

Dem ich arbeite, schreiben einen IRC-bot in C, und lief in einen Haken.

In meiner main-Funktion Erzeuge ich meine Buchse und verbinden alle, die gerne Sachen. Dann habe ich eine (fast) unendliche Schleife zu Lesen, was gesendet wird wieder vom server. Ich gebe dann was Lesen Sie aus, um eine Hilfsfunktion, processLine(char *line) – das problem ist, dass der folgende code liest, bis mein Puffer voll ist – ich will es nur text Lesen, bis ein newline (\n) oder carriage return (\r) Auftritt (also Ende der Zeile),

   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);
    }

Was am Ende passiert, ist, dass viele Linien eingeklemmt werden alle zusammen, und ich kann nicht verarbeiten, die Zeilen richtig, wenn das passiert.

Wenn Sie nicht vertraut mit IRC-Protokollen, eine kurze Zusammenfassung wäre, dass, wenn eine Nachricht gesendet wird, ist es oft sieht wie folgt aus: :[email protected] PRIVMSG #someChannel :The rest on from here is the message sent...
und ein login-beachten Sie, zum Beispiel, ist so etwas wie dieses: :the.hostname.of.the.server ### bla some text bla mit ### als code(?) verwendet für die Verarbeitung – D. H. 372 ist ein Indikator, dass der folgende text ist Teil der Nachricht Des Tages.

Wenn es alle zusammengestaucht, ich kann nicht Lesen, was Zahl ist, für welche Linie kann ich da nicht finden, wo Sie eine Linie beginnt oder endet!

Ich würde schätzen, Hilfe bei diesem sehr viel!

P. S.: Dieser wird kompiliert/lief auf linux, aber ich will schließlich die Portierung auf windows, so bin ich so viel davon wie ich kann multi-Plattform.

P. S. S.: Hier ist meine processLine () – code:

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");
    }
}
InformationsquelleAutor FurryHead | 2011-05-22

2 Kommentare

  1. 11

    Den üblichen Weg, um sich mit diesem zu recv in eine persistente Puffer in Ihrer Anwendung, dann ziehen Sie eine einzelne Zeile aus, und verarbeiten Sie. Später können Sie die verbleibenden Zeilen in den Puffer vor dem Aufruf recv wieder. Beachten Sie, dass die Letzte Zeile in den Puffer nur teilweise erhalten; Sie müssen sich dieser Fall durch eine erneute Eingabe recv um die Linie abzuschließen.

    Hier ist ein Beispiel (völlig ungetestet! sieht auch für einen \n, nicht \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);
    }
    • scheint einfach genug. Wie würde ich es erneut eingeben, recv(), obwohl? Würde ich passiere einen char-Zeiger auf das Ende der teilweise text Lesen, d.h. wenn recv() liest nur 5 von 10 chars, übergeben Sie einen Zeiger auf die 6. position statt?
    • Hinzugefügt (ungetestet) – Beispiel
    • Oh wow. Ich habe bis auf dieses Projekt vor langer Zeit, das Gefühl, wie die meisten dieser ging direkt über meinem Kopf (das war es). Jetzt bin ich endlich wieder zurück kommen zu einem sehr ähnlichen Projekt (irc-bot wieder, aber ein wenig anders) und ich lese über diese, ohne es zu merken war dies mein thread. Ich habe schlug meinen Kopf gegen den Schreibtisch für die letzten 2 Tage, zu versuchen, dieses (fast genau das, was Sie schreiben), aber, seltsam genug, landete ich mit nur einem Charakter entfernt von zufälligen Positionen in der Linie. Seltsam. Sowieso, wollte nur noch einmal danke! Dies hat mir sehr geholfen!
    • funktioniert Super, aber (inbuf - line_start); sollte (line_start - inbuf);
  2. 7

    TCP bietet keine Sequenzierung der Art. Wie @bdonlan schon sagte, Sie implementieren sollten, so etwas wie:

    • Kontinuierlich recv aus der Steckdose in einen Puffer
    • Auf jeden recv prüfen, ob die empfangenen bytes enthalten eine \n
    • Wenn ein \n verwenden Sie alles, was Sie bis zu diesem Punkt aus dem Puffer (und klar, es)

    Habe ich nicht ein gutes Gefühl (ich habe irgendwo gelesen, man soll nicht mischen, low-level-I/O mit stdio I/O), aber Sie können möglicherweise verwenden Sie fdopen.

    Alles, was Sie tun müssen, würde ist

    • verwenden fdopen(3) verknüpft Ihre Buchse mit einem FILE *
    • verwenden setvbuf zu sagen, stdio, dass Sie wollen, dass es line-buffered – ( _IOLBF ) im Gegensatz zu den Standard-block-gepuffert.

    Zu diesem Zeitpunkt sollten Sie haben effektiv bewegt sich die Arbeit aus den Händen zu stdio. Dann könnte man über die Verwendung fgets auf der FILE *.

    • Tolle Idee, ich habe es ausprobiert und es funktioniert wunderbar. Ich habe zwei Fragen aber: Wie würde ich prüfen, ob Fehler in windows? In der Regel verwende ich WSAGetLastError() als windows-sockets verwenden, dass anstatt errno… Und fdopen()/setvbuf() funktioniert unter windows auch?
    • (update, wenn ich versuche zu verarbeiten errno unter linux mit diesem, es gibt mir eine Fehler-code 0 – ich weiß aber noch nicht was, dass entspricht)
    • ist standard; Windows hat _fdopen. Über die errno Teil, bei der Verwendung von stdio Fehler prüfen mit ferror, feof. Offensichtlich stellt dies weniger details als eine recv oder eine read tut. Der standard sagt, keine Funktion sollte errno auf 0, aber ich glaube, es bedeutet „Erfolg“. Also selbst wenn ein recv fehl, kann der tatsächliche fgets gelingt.
    • Oh, toll. Das warf gerade alle meine error-handling-code direkt aus dem Fenster! Trotzdem war es hilfreich, unabhängig. Danke!

Kostenlose Online-Tests