Lab 7: Client/Server

In this lab you'll extend your database program to be accessible over a network.

When you're done with this lab you will have done the following things:

  1. Get your computers talking to each other.
  2. Morph your database program into a database server
  3. Create a client to interact with the database over a network
  4. Test the limits of your database system

Before proceeding to the following lab instruction, please complete the prelab here if you haven't.

Create a Database Server

  1. Inspect server.c. Much of the code in here is similar to your previous implementation of local.c , but some is different.

  2. The First function to implement is runServer. This function sets up the server socket and listens for incoming connections (handling one at a time). Things to implement (Look in runServer()):

    • Use bind to bind the socket to an address
    • Use listen to make the socket listen for incoming requests
    • Inside the loop, repeatedly handle clients:
      • accept the connection on a client socket
      • call handleRemoteInput to interact with the client
      • close the client socket using shutdown and close
    • Be sure to check your function calls for error return values!
    • More details in the TODO comments.
  3. Test out the server using nc. Run the server (./server on your Linux) and leave it running. Open another Linux window (so now you have two terminals). In the new window, connect to your server on the same computer using:

    nc 127.0.0.1 20000
    

    127.0.0.1 means "this computer". What does the server do? Is it what you expected?

    Once you've got nc to cause your server to do something, try connecting to your server from your partner's laptop.

  4. Implement handleRemoteInput to 1) receive input from the client instead of from stdin and 2) to send messages to the client instead of printing to stdout. Some hints:

    • In this client-server communication, we will use the following conventions:
      • To communicate a string, the sender does not send the terminating \0. It is the job of the receiving party to appropriately null-terminate the data. (Remember that recv returns the number of bytes received.)
      • If a string was read in with fgets on stdin (and thus the string has a newline \n at the end), again it is the job of the receiving party to strip off this newline, if necessary.
    • sprintf() can be used to print a string into a buffer that you can send to a client. Use it much like printf, but you print into a char buffer instead of to the screen. For example:

      char buf[512];
      sprintf(buf, "The number is %d!\n", 15);
      // now buf contains "The number is 15!\n" (terminated with <code>\0</code>)</li>
      

  5. Implement do_remote_list_database and do_remote_find_all_matches. These will be very similar to the non-remote versions you implemented for lab 6.

  6. Test your server with the the nc client.

    nc 127.0.0.1 20000
    
  7. If the previous manual test looks good to you, you can run the script ./test_server.sh to do an automated test.

Failed to bind issue

In case you encounter this issue, most likely it is because the port (20000) that you want to open is occupied. To solve this problem, run the following command in Linux terminal:

fuser -k 20000/tcp

If the problem still persists, wait for 60 seconds (the timeout in Linux kernel) and try again.

Create a Client

The next step is to make your own version of nc. This will be very similar to the local.c file, but have significantly less code. The entire purpose of the client is to connect to a server and then send it whatever you type.

SIDE NOTE: As you work on implementing the client, you may see some strange behavior where the server's responses seem "behind" where they should be! For example, listing the database may only show the first entry and other entries show up after you type a second command. This is because sockets expect one send() to be paired with one recv(). It is OK if you don't fix this "delay" error in this lab, but think about ways to solve the problem.

Okay, let's begin:

  1. Open up client.c. There are a couple of functions with loops:

    • handleLocalInput should contain a loop that can only do two things: connect to a server or quit.
    • do_connect_to_server should contain a loop that will keep communicating with the server until it closes the connection. This client will allow you to connect to many servers in sequence.
    • To get the port number to connect. You need to convert some numbers you type from the keyboard to an int. To convert a number string (a string with all number characters) to an int, you can call atoi(buf), where buf is the number string. For example,

      char buf[4] = {'2', '4', '6', '\0'};
      int num = atoi(buf);
      // Now num will be 246.</li>
      

  2. Implement handleLocalInput. There's not much to do, and it will look like your handleLocalInput from lab 6 but with fewer commands. The commands to implement are documented in the file.

  3. Implement do_connect_to_server. This function does the following until the server closes the socket:

    • receive data from the server
    • print what it received
    • get a line of input from you
    • send your input to the server
  4. Test your client with the ./example_server executable provided.

  5. If the previous manual test looks good to you, you can run the script ./test_client.sh to do an automated test.

Test your client and server

Finally, make sure your server and your client can talk to each other.

Finishing the Lab

  1. Ensure your name and your partner's names are on all the files you edited.
  2. Make sure you passed both ./test_server.sh and ./test_client.sh
  3. Ensure both your names are on all of the files you edited. Both of you should submit a copy of the code on Gradescope.
  4. Upload any files you modified (server.c client.c) to Gradescope