2

I'm writing a simple IRC client program in C for self-teaching purposes, and am having trouble understanding the behavior of the read() function when called reading from a socket file descriptor.

The following code snippet works and prints the same output as running

$ echo "NICK gertrudes\r\nUSER a 0 * d\r\n" | nc chat.freenode.net 6667

in the terminal, which is the same as my program prints so far:

while ((n = read(sockfd, buffer, sizeof(buffer)-1)) > 0) {
    printf("\nloop\n");
    buffer[n] = '\0';
    if (fputs(buffer, stdout) == EOF)
        error("fputs");
}
if (n < 0)
    error("reading from socket");

printf("out of the loop\n");

What I fail to understand is why the program never gets to the final printf call, and rather sits there as if waiting for more from the server. Does that mean that the last reply was longer than 0 and the IRC server just won't send anything new until I send another command?

If so (at the risk of going off-topic here), and read() is blocking, where would I write the logic of sending commands to the server while the program is waiting for that call to return?

sleighty
  • 895
  • 9
  • 29
  • 1
    To get you started: reading a socket will return a zero if the peer has disconnected the connection (you don't handle that)... It returns a -1 if there is an error (you don't handle that) – TonyB Mar 04 '18 at 00:59
  • Ok, that's what I was thinking. I'm having trouble wrapping my head around how to handle that read call. How can I get input from the user and write it to the socket while also monitoring that read call to make sure it hasn't been disconnected? – sleighty Mar 04 '18 at 01:02
  • You're *doing* it. You're reading input until there is an end of stream or error. Unclear what you're asking. – user207421 Mar 04 '18 at 01:04
  • 1
    If your question is how to handle waiting for a client connection, waiting for one or more client messages, and sending responses ... simultaneously, look at what "select()" or "poll()" provide you. – TonyB Mar 04 '18 at 01:07
  • Sorry, what I meant is how can I read input from stdin (or something else *local*) while reading from the socket. My idea for where this program is going is to write everything I read from the socket to a file and get input from stdin and send that to the socket (likely send chat messages from stdin to IRC server). @TonyB I'll take a look, thanks! – sleighty Mar 04 '18 at 01:08
  • 2
    (this is a drastic simplification, but should help you get the gist) With select() or poll() you can give it a list of descriptors that you want to read + a time-limit you want to wait, and select will return when at least one of the list is ready to be read... I think of it as "select returns when a timeout has occurred, or one of the descriptors will 'not block' on a read()"... in other words you could give select your socket descriptor and stdin_fileno, and it wouldn't return until you could read one without blocking... read the man page and search for examples. – TonyB Mar 04 '18 at 01:17
  • @TonyB awesome, I've been doing some reading on select and got a pretty clear view of next steps. Thanks a lot! – sleighty Mar 04 '18 at 01:20
  • 1
    Learn about [poll(2)](http://man7.org/linux/man-pages/man2/poll.2.html) – Basile Starynkevitch Mar 04 '18 at 02:14

2 Answers2

2

What I fail to understand is why the program never gets to the final printf call, and rather sits there as if waiting for more from the server.

It is waitng for more from the server. read() will return zero when the peer disconnects, and not before.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • what if peer dies abruptly? I have observed that if peer dies then `read()` keeps on waiting forever. – Cracken Jan 19 '21 at 14:33
1

Despite your program not being complete, there are several things that you are wrongly assuming. Let's comment these in your code.

while ((n = read(sockfd, buffer, sizeof(buffer)-1)) > 0) {
  • It's good to read sizeof(buffer)-1 if you plan to complete it with a \0 byte, but think that you can receive a \0 from the socket, if you want to be general, don't assume you are always reading text. Many security exploits come from errors like this. The programmer assumes (erroneously) that the data is ascii text, and someone exploits a buffer overrun (this is not the case) or something illegal, feeding a lot of null characters to make it fail.

    printf("\nloop\n");
    buffer[n] = '\0';
    if (fputs(buffer, stdout) == EOF)
    
  • This is a VERY common mistake... you are used to see that when you put a \n at the end of a buffer, stdio prints everything until the last buffer as soon as it sees it. Well, for this to happen, stdio checks if the descriptor is associated to a terminal (by means of an ioctl(2) call, or a call to isatty(3)). This is no longer true with sockets, so probably your buffer has been copied to stdio buffer, and stdio is waiting for the buffer to fill or you to explicitly flush the buffer with fflush(3) before calling write(2) to send all the data over it.

        error("fputs");
    
  • Do a fflush(stdout); at this point, so you are sure all your data is sent to the peer, before continuing, or don't use stdio at all (use simple write(2) calls, until you are proficient enough to prepare a thread that select(2)s on the socket to feed more data as soon as it is ready to accept more data)

    }
    if (n < 0)
        error("reading from socket");
    
    printf("out of the loop\n");
    
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31