Lab 2: File Transfer Client and Server
Create repository on GitHub: https://classroom.github.com/a/FQhLlcpv
Note: You will implement this lab individually. However, you should choose a teammate to share a .proto
file with so that your client and server applications are compatible (at least in theory). You will find that simply using the same protocol does not guarantee compatibility, but should get you fairly close.
In this lab, you’ll create a file transfer suite (including both a client and server application) similar to the File Transfer Protocol (FTP). You’ll practice using TCP for communication and Google Protocol Buffers to serialize/deserialize data for transmission over the network.
While protocol buffers will facilitate our control plane – metadata such as operation types, checksums, file names, and sizes – the file data will be transferred on the data plane as a raw stream of bytes over the TCP channel.
Let’s take a look at the server and client functionality.
Server
The server will listen for client connections and either store or retrieve files.
# Usage:
./server listen-port download-dir
# Or, for example, listen on port 9898 and store files in './stuff':
./server 9898 ./stuff/
Note: [download-dir]
is optional; if not supplied by the user, use the current working directory (.
).
Here’s the general workflow for the server component:
- Start up and make sure storage directory exists
- Listen on the specified port for incoming client connections
- Handle each request with a separate goroutine (to allow multiple client connections)
Logic branches from here depending on whether the client wants to store or retrieve a file.
To store a file:
- Make sure the file doesn’t already exist (refuse to overwrite existing files)
- Ensure there is enough space available on the disk
- Send an “OK” response to the client so it knows it can begin sending the file
- Receive data stream and store the file
- Verify its checksum against the checksum sent by the client
- Respond to the client with the status of the transfer (success or failure)
- Disconnect the client
To retrieve a file:
- Ensure the file requested actually exists
- Send a response back to the client with the file’s size and checksum (or indicate failure if it doesn’t exit)
- Begin streaming file to the client
- Disconnect the client when the transfer is complete
Client
The client application either sends files to the server to be stored or requests a file name to be retrieved.
# Usage:
./client host:port action file-name [destination-dir]
# Example storage (PUT):
./client localhost:9898 put /some/file.jpg
# Example retrieval (GET):
./client orion12:9898 get file.jpg /tmp/my/stuff/
Note 1: both the client and server do not support the notion of folders or directories, so retrieval operations will not include a full path (just the file’s name that was stored on the server).
Note 2: The [destination-dir]
is optional when doing a retrieval; if not supplied by the user, use the current working directory (.
).
Here’s the general workflow for the client component:
- Start up and connect to the server
- Based on the action request by the user, send an appropriate message:
- Storage request with the file name, size, and checksum
- Retrieval request with the file name
To store a file:
- Wait for the server’s response to the storage request. If it accepts, begin sending the data
- After the transfer is complete, wait for acknowledgement by the server
- This ensures the file was successfully transferred and the checksum matched
- The server will terminate the connection. The client exits.
To retrieve a file:
- Wait for the server’s response to the retrieval request.
- Create the file and begin storing its data
- After the transfer is complete (the server will disconnect), verify the checksum of the file and report success/failure.
Hints
- Go includes a few hash algorithms that you can use to verify file integrity.
- Client sessions are managed by individual goroutines. It’s fine to have a goroutine block while it waits for data from the client; other goroutines will continue to make progress.
- Don’t forget to write helper functions to simplify logic that you see repeated often!
Submission
- Check your code (including your
.proto
file) into your repository. - With the teammate you selected earlier, attempt to make your client and server compatible, i.e., your client works with their server and their client works with your server.
- If you don’t have a teammate, post a copy of your
.proto
file on Campuswire or choose one that is already posted to try to support with your implementation. - List any changes you made while doing this in your README.
- If you don’t have a teammate, post a copy of your
Note: don’t stress if you can’t get your client/server to work with someone else’s. This is merely an exercise. After you have submitted your code, you are welcome to share your implementation with others.