15-441 IRC Server FAQ

General programming questions

  1. Should we treat metric prefixes such as kilo as being 1000x or as 1024x? Should we treat kilobytes differently from kilobits?

    If you're expressing things in bits/s or bytes/s please use 1000. If you're expressing the size of a file, please use 1024 bytes. A general approach is to refer to network speeds in bits/sec ("100 megabits/sec") and to refer to file sizes in bytes ("a 10 gigabyte file"), to help emphasize that one is using the networking convention and the other the 1024 convention.

    An alternative option, which you are welcome to use (and, in fact, encouraged), is to refer to the file sizes using the binary prefixes "kibi" (Ki), "mebi" (Mi), "gibi" (Gi), and so on. Wikipedia has a nice article about the SI prefixes in computing. They are now an official IEEE standard, and it would be really great if their use took off.

  2. What are the flags that can and should be used with gcc?
    As you probably know from 213: -W is the warning level, -O is the optimization level, and -g is the debugging tables. -g produces a much bigger file. In fact, you probably want to strip the debugging information out before giving it to the public. As for optimizations, you probably will not gain much performance from -O2. You will be making many, many system calls, and system calls are slow anyway you look at them. No amount of optimization is going to speed those up. While developing:
  3. Is there some place to get some pointers to implementing timers in C?
    The select() call has a timeout parameter. If you maintain a variable containing the global time at which you need to wakeup and broadcast a packet, it's easy to calculate the difference between the current time and the "wakeup time," and pass that as the timeout to select. Then, when select returns, you can check to see if the current time is later than the wakeup time, and if so, do whatever it is that you needed to so.
  4. Is there not some required library we should tell gcc to use while linking so that select can manage time?
    The headers you need for select are outlined in the manual. Try "man select" and you will find everything you need. Note that when using a timeout in select, Linux will change the time structure to reflect the remaining amount of time. For example, I tell select to timeout after 60 seconds, but select returns after 40 seconds. The time structure I handed it will now read 20 seconds. This behavior is system-dependant and your code should not rely on it. To ensure portability, you should check the current time using gettimeofday() and then, as the manual page suggests, reset the time structure before each call.

Socket programming questions

  1. When one of the clients disconnects, we get a broken pipe error. For some reason select() puts the disconnected fd in the set of readfds and writefds. Is there a way of detecting when a client has disconnected?
    See the UNIX Socket FAQ for more information.

    There are two errors of interest: EPIPE and ECONNRESET. When a client disconnects, your program will get a SIGPIPE and errno will be set to EPIPE. Signals have default handlers unless you specify your own. SIGPIPE will simply exit, an undesirable feature. I suggest ignoring SIGPIPE:

    signal(SIGPIPE, SIG_IGN); /* Block SIGPIPE Signals */

    This will prevent your server from dying, but select() will still mark the fd as readable/writable (section 2.22 of the above link).

    If read() returns -1 and sets errno to ECONNRESET ("Connection reset by peer"), you should remove the client. The other case of read() is EOF (ie 0), which also means the connection is closed, and you should remove the client. But remember that for robustness, your server should gracefully handle any failure returned by read.

    If send() or write() returns -1 and sets errno to EPIPE, then you should remove the client.

  2. Can we assume that the functions rt_recvfrom and rt_sendto that are provided are non-blocking?
    The basic answer is that these functions are simply wrappers around the real recvfrom and sendto functions, so they will behave the same way the normal functions will. Ergo:

    You can assume that rt_sendto is non-blocking, because it's UDP. If a UDP socket is over-full, it just starts dropping packets. So it may fail, but it shouldn't block. That said, if you already have code that handles a buffered socket for writing stuff, you're welcome to reuse it - it will just never block.

    You may not assume that rt_recvfrom is non-blocking. It will block if there are no packets waiting.

  3. In the write-up, it states that we need to convert any multi-byte integer field to network byte order. Are we suppose to convert the user entries and channel entries to network byte order too? Is there a function that does this for char *?

    For example:

    (Note: This is only to give you the idea of how to do it!)

       ... somewhere earlier, you defined a struct with the packet contents ...
       struct foo {
         u_int32_t type;
         u_int32_t last_node_id;
         u_int32_t seqnum;
         char nick[16];
         ...
       }
    
       /* Convert host to network */
        /* packet is a char * pointing to space for the packet */
        struct foo *p;
    
        p = (struct foo *)packet;
    
        p->type   = htonl( type );
        p->last_node_id = htonl(last_node_id);
        p->seqnum = htonl( seqnum );
    
        strncpy(p->nick, nick,  16);
    

    To unmarshal the messages at the receiver:

        type = ntohl(p->type);
        last_node_id = ntohl(p->last_node_id);
        seqnum = ntohl(p->seqnum);
        strncpy(nick, p->nick, 16);
    
  4. What is the first parameter, int s, in sendto?
    It is the file descriptor (fd) of the server's connection.
  5. Why does sendto() require the sockaddr struct, when send() doesn't?
    Send operates on a connection, meaning the socket already knows where the data is headed. Before calling send, you must have already called connect.
  6. We're a little confused about the C time structures. The select() man page is a bit confusing about what is in a "timeval." How do we use these?
    Select takes a struct timeval. This struct has two members:

    Just set the timeval.tv_sec and timeval.tv_usec before calling select. You should not rely on the values in this structure after calling select (see earlier question about timers). We suggest something like:

      struct timeval tv_start, tv_end;
    
      gettimeofday(&tv_start);
      select(...);
      gettimeofday(&tv_end);
    
      and then, if you need
    
      time_waited = tv_difference(&tv_start, &tv_end);
    

    (tv_difference is a function you have to write, but it's trivial).

  7. When we start the server, it tries to connect to its neighbors. If the other servers haven't started yet (or won't exist), doesn't this connection attempt block? If so, how do we prevent this from making our server block, or do we need to?
    Yes, you need to. Set the file descriptor to non-blocking before you attempt to connect. connect() will return immediately with a return value of -1, and will set errno to EINPROGRESS. After that, you can select() on the socket to make sure that you only operate on it after the TCP connection has completed. (Or you'll get an error if the connection fails).

    See the man page for connect for more details.

  8. When we reply with multiple lines, should we use multiple write(...) calls or one write call with \n's? Will the grading scripts accept either?
    Recall that TCP is a stream oriented protocol and it could "repackage" the write calls you perform arbitrarily into packets. in arbitrary ways. The read calls performed at the other end may return data that is not split up in the same way as the write calls. Therefore, this means that there is no functional difference between using multiple write calls or one write call. The grading scripts must accept either. Note also that your server should not depend on TCP behaving in such a manner when reading or writing: the grading scripts may try to do devious things to cause read() to return smaller or large amounts of data, etc.

    Note that the above is not true for UDP. UDP sends provide strict message boundaries. What you send in one sendto call is exactly what you receive at the other end in a single recvfrom call.

IRC message formatting




  1. Routing protocol questions


    Last updated: Tue Jan 19 16:17:23 -0500 2010 [validate xhtml]