CSCI 631 Section 1 Assignment 4 Implementation Notes

This assignment is an enhancement of Assignment 3. Start by making a copy of your Assignment 3 files and rename them accordingly.

Implement the additional functionality for Assignment 4 by refactoring The logic in your doLookupBooks() and doServ() funtions and add the logic for an iterative UDP server as shown in UNP 8.15. It might be helpful to get a copy of the udpcliserv/udpservselect01.c file from the book's source code and review the various UDP Client-Server notes on the main CSCI 631 web site.

Students in section 1 must implement their TCP client application according to the following usage statement:

Usage: prog4_client_tcp IP-number [port-number]

Students in section 1 must implement their UDP client application according to the following usage statement:

Usage: prog4_client_udp IP-number [port-number]

In both cases the IP number to connect to is required and the port number is optional. The default port number shall be 9880.

Students in section 1 will implement their server application according to the following usage statement:

Usage: prog4_server book-database-filename [port-number]

Note that both the TCP listener and the UDP server require a port number. We will use the same port number for both. This is perfectly acceptable and is quite common. Doing so is explicitly allowed because TCP and UDP sockets have a different protocol value in their socket() calls. Thus they are differentiated by the operating system based on protocol.

prog4_server

The first step of this assignment is to update the server in two phases. Phase-I is to refactor the book lookup logic so that it can be used for both the TCP and UDP server logic.

Note that the man page for fscanf() discusses the following conversion option:

   Each conversion specification in format begins with either the  charac-
   ter '%' or the character sequence "%n$" (see below for the distinction)
   followed by:

   *  An  optional  'a'  character.   This  is used with string conversions, and relieves the
      caller of the need to allocate a corresponding  buffer  to  hold  the  input:  instead,
      scanf()  allocates  a buffer of sufficient size, and assigns the address of this buffer
      to the corresponding pointer argument, which should be a pointer to a char  *  variable
      (this  variable  does  not  need to be initialized before the call).  The caller should
      subsequently free(3) this buffer when it is no longer required.  This is a  GNU  exten-
      sion;  C99 employs the 'a' character as a conversion specifier (and it can also be used
      as such in the GNU implementation).

What this means is that fscanf(), on an OS that uses the GNU C library such as Linux, provides a way in which you can let fscanf() perform a malloc() and return the space needed to contain the strings you are extracting from the input lines. This means that you can not run into a buffer overrun problem!

Here is an example of how to use fscanf() with %m to allocate properly sized buffers to the title and author fields in the 'database'. Note that since they are malloc()'d, they have to be free()'d. Else your program will leak memeory. (Note also that I used %m rather than %a. This is because of the notes at the end of the man page for fscanf().)

You are free to use an alternate approach to scanning the book database. As before, you can use fixed-sized buffers to hold the author and title. This assignment is not primairly concerned with reading the file.

static int doLookupBook(const char *bookdb, const char *title, char *author, size_t authorLen)
{
    fprintf(stderr, "Looking up: '%s'\n", title);

    // open bookdb
    FILE *db = fopen(bookdb, "r");

    // look it up
    int match = -1;
    char *ba;
    char *bt;
    while(match && fscanf(db, " %m[^:]:%m[^:]:", &bt, &ba) == 2)
    {
        fprintf(stderr, "checking '%s', '%s'\n", bt, ba);
        if ((match = strcmp(bt, title)) == 0)
        {
            strncpy(author, ba, authorLen-1);
            author[authorLen-1] = '\0';
        }
        free(ba);
        free(bt);
    }
    fclose(db);
    return(!match);
}

To use this new function, you must refactor doLookupBooks() into doLookupBooksTCP() and doLookupBook(). Then verify that your program still works the same as it did before (it should, as you would not have done anything other than to change its structure to an equivelant form.

Phase-II. Incorporate the select() and UDP server logic from UNP 8.15.

Your UDP server logic should read a packet that contains the title (and an optional sequence number) and respond with the author (and an optional sequence number). The title packet MUST not be required to include a null-character string terminator, nor SHOULD it contain trailing whitespace.

prog4_client_tcp

This is the same as prog3_client. Just rename prog3_client.c to prog4_client_tcp.c

Make sure that your prog4_client_tcp terminates itself when you press ^D and send it an EOF condition.

prog4_client_udp

Use the code in UNP Figure 8.7 and 8.8 as a guide for how to write your UDP client and put your main() in prog4_client_udp.c and a UDP query function to query.c and query.h with the following signature:

int doQueryUDP(const char *ip, uint16_t port, const char *title, char *author, size_t authorlen);

As with the TCP client, trim off any whitespace from the keyboard input before sending the title to your doQueryUDP() function. The server must receive a packet with EXACTLY the number of characters as are required for the title. For example the title 1984 must arrive at your server such that the last byte is the '4' character. It must NOT be terminated by a null character. It must not contain a newline. Verify this by printing the length AND the value received on the server with quotes around it to verify. Then remove such debuging output when you know it is correct.

Notice that there is no value in using Readline() in your UDP client app because it does not read lines... it reads datagrams.

There is no reason to trim whitespace off the UDP responses from the server. You will have written a proper server as discussed above that returns the author with no triling whitespace. Simply add a null terminating character to the response datagram that your client receives from your server and return it from doQueryUDP().

If you did Assignment 3 properly, you should be able to copy your prog3_client.c to prog4_client_udp.c and do nothing more than change doQuery to doQueryUDP

In the first version of your prog4_client_udp, just get it to work. By that I mean just call sendto() and recvfrom() without worrying about timeouts.

Once you have that working you can optionally add a select(2) that provides a timeout so that your client will not get stuck if the server response never arrives.

Make sure that your prog4_client_udp terminates itself when you press ^D.

Complex Testing

In order to verify that your server can properly handle both UDP and TCP clients at the same time you should use multiple instances of both your TCP and UDP clients that are connected/querying your server at the same time.

Makefile

Here is the Makefile that MUST be used by students in section 1 to compile the application code for this assignment. Note that this Makefile will produce client application executables named prog4_client_tcp, prog4_client_udp and a server application executable named prog4_server.

How To Hand In This Assignment

To hand in this assignment, execute the following command on tiger:

mail_prog.631 prog4_client_tcp.c prog4_client_udp.c wrapper.c wrapper.h in_out.c in_out.h query.c query.h prog4_server.c serv.c serv.h 

Last modified: 2018-05-28 11:04:55 CDT