Exercise 8: Client-Side Networking

Due:   Friday, August 14 by 11:59 pm
Rating:   3 (note)
Learning Objectives:

Problem Description

Write a C++ program that connects (via TCP) to a server specified by a user-supplied hostname and port number, sends the bytes of a specified local file to the server, and then closes the connection and exits. Your program should accept the command-line arguments in the following order:

  1. The hostname of the server
  2. The port number of the server
  3. The name of a local file

We provide WrappedRead() and WrappedWrite() for transferring the file. These functions use the POSIX read() and write() functions and handle recoverable errors and partial writes for you.

An example execution of the completed application is:

$ ./ex8 localhost 5555 test.txt

Files

The starter contains four relevant files:

  • SocketUtil.h : Provides the public interface for various client-side networking utility functions. Do not modify this.
  • SocketUtil.cc : Contains empty implementations of LookupName() and Connect(). The other utility functions are provided for you. You complete part of this.
  • ex8.cc : Contains an empty main function for the client-side networking program. You complete this.
  • Makefile : Provided for your convenience in compiling the executable ex8. Do not modify this.

What to Implement

Complete the exercise in this order:

  1. First, complete SocketUtil.cc by implementing LookupName(), which resolves the hostname and initializes a socket address with the requested port, and Connect(), which creates a TCP socket and connects it to that address.
  2. Then, complete main() in ex8.cc. It should validate the command-line arguments, open the local file, use your socket utility functions to connect to the server, transfer the entire file, and close all open file descriptors.

We provide the implementations of WrappedRead() and WrappedWrite(). Use these functions when transferring the file and do not modify them.

Implementation Notes

Socket Address Types

A network address has a different concrete C structure depending on its protocol: IPv4 uses sockaddr_in, while IPv6 uses sockaddr_in6. A sockaddr_storage is a generic container that is large enough and properly aligned to hold either kind of address. This lets LookupName() return one address without requiring its caller to know in advance whether DNS will produce IPv4 or IPv6.

The ss_family field records which address family is actually stored. Because IPv4 and IPv6 addresses have different sizes, the address must travel with its length; that is why LookupName() also fills ret_addrlen and Connect() receives addrlen.

POSIX functions such as connect() accept the common interface type sockaddr*. When passing a sockaddr_storage to one of these functions, cast its address to the required sockaddr* type and pass the matching length. The family and length tell the operating system how to interpret the bytes in the generic container.

Code Adaptation

Feel free to adapt sample code from lecture and section as part of your solution if it helps, but be sure you understand what your code does when you're done.

User Input

Require exactly three command-line arguments after the program name. The hostname must resolve to an address, the port must be a decimal integer from 1 through 65535, and the local file must be readable. Reject input that does not meet these requirements.

Error Handling & Robustness

Check the return value of each function that can fail and return a failure status when an operation cannot be completed. Print a useful message to standard error and return EXIT_FAILURE from main(). The provided wrappers already retry read() and write() after recoverable errors (EAGAIN and EINTR). Make sure that you close open file descriptors in all possible cases, including when connect() or a file transfer fails.

Build and Test

Build

Build the ex8 executable with the provided Makefile:

$ make

You can remove generated files with make clean.

Server Setup

To test your program, you will need to set up a server to receive the data that your ex8 executable will send. The recommended way to do this is using the nc utility:

$ nc -l <port> > output.bytes

This command will run a netcat listener (-l) on port <port> (e.g., 5555), which needs to match the port you provide to the ex8 executable, and redirect any received bytes to the file output.bytes. Note that this will create the file if it didn't exist or overwrite if it does exist.

Local Testing

If you are running the server on the same computer/host that you are testing your code on, you can use the special loop-back IP address 127.0.0.1 or localhost to refer to the same host. Please note that each attu (e.g., attu1, attu2) counts as a separate host, so if you are testing on attu, both client and server must be running on the same one for this to work. To log into a specific attu machine, run:

$ ssh <netid>@attu<#>.cs.washington.edu

where <#> should be replaced by a number 1 – 8.

Check the Result

After the client and server exit, compare the received file with the original. cmp produces no output when the files contain exactly the same bytes:

$ cmp test.txt output.bytes

Test more than the successful example. Your program should also handle an empty file, a nonexistent file, a missing argument, an invalid hostname or port, and a server that is not listening.

Style Focus

Preserve the Interface

Do not modify SocketUtil.h, the provided wrapper implementations, or any function signature. The autograder builds your code against the provided interface.

C/C++ Idioms

The POSIX networking API requires some C interfaces, but use C++ idioms elsewhere. For example, use stream output instead of printf() and C++ casts instead of C-style casts.

Submission

Submit your work by creating an ex8-submit tag in your exercise repo before the deadline. These files must live at the exact paths below, including capitalization:

  • ex8/ex8.cc
  • ex8/SocketUtil.cc

Other files in ex8 are ignored, so you may keep your test files there.


Requirements for Full Credit

For full credit, your submission must:

  • Compile without errors or warnings on CSE Linux machines using the provided Makefile.
  • Send every byte of the requested file to the server in the original order.
  • Handle invalid input and failed file or network operations without leaking file descriptors.
  • Have no runtime errors, memory leaks, or memory errors under valgrind.
  • Have a comment at the top of both submitted .cc files with your name(s) and CSE or UW email address(es).
  • Follow the class style guidelines and produce no complaints from cpplint.py.