jack_the_beast
jack_the_beast

Reputation: 1971

C: sockets: can't read the whole server response

I'm programming in C an IRC chat client. everything it's working well except I can't read the whole answer sent by the server. here's the code:

char buffer[2048];
write_on_screen(current_page(), "LOG COMMAND", command);
write(sockfd, command, strlen(command)); //write to socket
bzero(buffer, sizeof(buffer));
read(sockfd, buffer, sizeof(buffer));
write_on_screen(current_page(), "RESPONSE", buffer);
return buffer;

most of the time buffer will contain just a piece of the response (which is shorter than 2048 bytes) and other times it contains nothing. in both cases if I do another read() after the first one, it returns me the rest of the answer or another small piece (and then I've to do another read() again). if I put a sleep(1) between write() and read() I get the whole answer, but I'm sure this not a good pratice. Is there some way I can avoid this?

thank you in advance

Upvotes: 2

Views: 3007

Answers (3)

alk
alk

Reputation: 70883

You need to loop around read() until a CRLF had been detected.

A possible way to do this would be:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

ssize_t read_until_crlf(int sd, char * p, size_t s, int break_on_interupt)
{
  ssize_t bytes_read = 0;
  ssize_t result = 0;
  int read_cr = 0;
  int read_crlf = 0;

  while (bytes_read < s)
  {
    result = read(sd, p + bytes_read, 1);
    if (-1 == result)
    {
      if ((EAGAIN == errno) || (EWOULDBLOCK == errno))
      {
        continue;
      }
      else if (EINTR == errno)
      {
        if (break_on_interupt)
        {
          break;
        }

        continue;
      }
      else
      {
        perror("read() failed");
        break;
      }
    }
    else if (0 == result)
    {
      break; /* peer disconnected */
    }

    if ('\r' == p[bytes_read])
    {
      read_cr = 1;
    }
    else if (('\n' == p[bytes_read]) && read_cr)
    {
      read_crlf = 1;
      break; /* CRLF detected */
    }
    else
    {
      read_cr = 0;
    }

    ++bytes_read;
  }

  if (!read_crlf)
  {
    result = -1; /* Buffer full without having read a CRLF. */
    errno = ENOSPC; /* ... or whatever might suite. */
  }

  return (0 >= result) ?result :bytes_read;
}

Call it like this:

#include <stdio.h>

ssize_t read_until_crlf(int sd, char * p, size_t s, int break_on_interupt);

int main(void)
{
  int sd = -1;

  /* init sd here */

  {
    char line[2048] = "";
    ssize_t result = read_until_crlf(sd, line, sizeof line, 0);
    if (-1 == result)
    {
      perror("read_until_newline() failed");
    }

    printf("read '%s'\n", line);
  }

  return 0;
}

Upvotes: 1

GoBusto
GoBusto

Reputation: 4778

According to RFC-1459, a single line of text in IRC can contain up to 512 characters and is terminated by a CRLF (\r\n) pair. However:

  • You're not guaranteed to receive exactly 512 bytes each time. For example, you might receive a comparatively short message from someone else one in the channel: Hi!
  • Related to the above: A group of 512 bytes might represent more than one message. For example, the buffer might contain a whole line, plus part of the next line: PRIVMSG <msgtarget> <message>\r\nPRIVMS

Given that you could have zero-or-more complete lines plus zero-or-one incomplete lines in your buffer[] at any time, you could try doing something along the lines of:

char buffer[2048];

while(keep_going)
{

  char **lines;
  int i, num_lines;

  // Receive data from the internet.
  receiveData(buffer);

  // Create an array of all COMPLETE lines in the buffer (split on \r\n).
  lines = getCompleteLines(buffer, &num_lines);
  removeCompleteLinesFromBuffer(buffer);

  // Handle each COMPLETE line in the array.
  for (i = 0; i < num_lines; ++i) { handle_line(lines[i]); }
  freeLines(lines);

}

This would allow you to handle zero or more complete lines in one go, with any incomplete line (i.e anything after the final \r\n pair) being kept around until the next call to receiveData().

Upvotes: 1

user207421
user207421

Reputation: 310850

You're making the usual mistakes. It is impossible to write correct network code without storing the result of read() or recv() into a variable. You have to:

  1. Check it for -1, and if so look at errno to see whether was fatal, which it almost always is except for EAGAIN/EWOULDBLOCK, and if fatal close the socket and abandon the process.
  2. Check it for zero, which means the peer disconnected. Again you must close the socket and abandon the process.
  3. Use it as the count of bytes actually received. These functions are not obliged nor guaranteed to fill the buffer. Their contract in blocking mode is that they block until an error, end of stream, or at least one byte is transferred. If you're expecting more than one byte, you normally have to loop until you get it.

Upvotes: 2

Related Questions