2

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

jack_the_beast
  • 1,838
  • 4
  • 34
  • 67
  • 2
    `read()`ing from a socket does not necessarily return as much as told to read. This is per definition. Read the documentation closely: `man 3 read` or `man recv`. – alk Feb 17 '15 at 08:56
  • A socket is a stream, but the data is sent in packets. That means that it can arrive in pieces. If you want to make a chat server you need to handle reads and writes asynchronuosly. You can do it in one thread using `select` (or `epoll` if your target environment is limited to Linux) and non-blocking I/O, or you can have one thread for each incoming connection and one for sending and use blocking IO. – Klas Lindbäck Feb 17 '15 at 09:02

3 Answers3

2

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.
user207421
  • 305,947
  • 44
  • 307
  • 483
1

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().

GoBusto
  • 4,632
  • 6
  • 28
  • 45
1

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;
}
alk
  • 69,737
  • 10
  • 105
  • 255