In this programming project, you will be writing (1) a simple file server that responds to client requests, and (2) a client that requests the file(s) from the server. You are given sample file client and server codes in Figure 6-6 of the Tanenbaum & Wetherall textbook. Your task is to update that code to satisfy the following features. The codes in Figure 6-6 will not work in a typical system. We have updated the codes and made it more workable. The code package with a Makefile in it is available via Webcourses in a file named file- server-files.zip. Be sure to download that zip file first.

The Basic Assignment

For the basic part of the assignment, improve the file client and server codes as follows:

  • Make the client write the received data bytes to a file with the name identified by the client's user. Your client must write the bytes to the file in order and without introducing any other characters or bytes. It must be able to successfully write a binary (not just text) file received from the server.
  • Make the server print out debugging messages if the DEBUG global variable is set to 1. Make sure to define this DEBUG variable and its default value should be 0 - indicating no debugging messages to be printed. The followings must be printed out by the server:
    • Before starting to transfer the file:
      Sending < filename> to < client's-IP-address>
      Hint: For obtaining the clients IP address, look at the man pages of the accept() socket API.
    • During the file transfer at every 10% progress:
      Sent 10% of < filename>
      Sent 20% of < filename>
      Sent 30% of < filename>
      ...
    • When the file transfer is completed:
      Finished sending < filename> to < client's-IP-address>
  • Allow the client to add arguments that specify a byte range instead of requesting to download the whole file. The usage of byte range should be as: "client server-name -s START_BYTE -e END_BYTE file-name". Here, START_BYTE and END_BYTE will be integer and user want to retrieve this block of bytes of the entire file from the server. Remember, byte flags are not mandatory for the user and user can simply ignore the flags to request the entire file. This command will return the entire file: client server-name file-name
  • Add error detection such that the server responds back to the client with an error if the requested file does not exist. Show error message if user requests for an invalid byte range.
  • Add a client flag "-w" that allows the file to be written to the server. When the client is being executed from the command prompt, the flag will optionally be available to the user. The usage of the client process should be as: client server-name [-w] file-name. If the -w flag is given, the server should interpret that as the file will be uploaded/written to the server side. Note that, the server should check if file exists in its local directory first before it allows the client to send the file to the server side.
  • Show help message if client gives wrong argument or fewer arguments. It's essentially printing some sample commands youre expecting from user.

Starter Codes

client.c

/* This page contains a client program that can request a file from the server program
* on the next page. The server responds by sending the whole file.
*/

#include "file-server.h"

int main(int argc, char **argv)
{
int c, s, bytes;
char buf[BUF_SIZE]; /* buffer for incoming file */
struct hostent *h; /* info about server */
struct sockaddr_in channel; /* holds IP address */

if (argc != 3) fatal("Usage: client server-name file-name");
h = gethostbyname(argv[1]); /* look up host's IP address */
if (!h) fatal("gethostbyname failed");

s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s <0) fatal("socket");
memset(&channel, 0, sizeof(channel));
channel.sin_family= AF_INET;
memcpy(&channel.sin_addr.s_addr, h->h_addr, h->h_length);
channel.sin_port= htons(SERVER_PORT);

c = connect(s, (struct sockaddr *) &channel, sizeof(channel));
if (c < 0) fatal("connect failed");

/* Connection is now established. Send file name including 0 byte at end. */
write(s, argv[2], strlen(argv[2])+1);

/* Go get the file and write it to standard output. */
while (1) {
bytes = read(s, buf, BUF_SIZE); /* read from socket */
if (bytes <= 0) exit(0); /* check for end of file */
write(1, buf, bytes); /* write to standard output */
}
}

file-server.h

/* This page contains a client program that can request a file from the server program
* on the next page. The server responds by sending the whole file.
*/
#include < stdio.h>
#include < stdlib.h>
#include < string.h>
#include < unistd.h>

#include < sys/types.h>
#include < sys/socket.h>
#include < netinet/in.h>
#include < netdb.h>

#define SERVER_PORT 2345 /* arbitrary, but client & server must agree */
#define BUF_SIZE 4096 /* block transfer size */

void fatal(char *string)
{
printf("%s\n", string);
exit(1);
}

server.c

/* This is the server code */
#include "file-server.h"
#include < sys/fcntl.h>

#define QUEUE_SIZE 10

int main(int argc, char *argv[])
{
int s, b, l, fd, sa, bytes, on = 1;
char buf[BUF_SIZE]; /* buffer for outgoing file */
struct sockaddr_in channel; /* holds IP address */

/* Build address structure to bind to socket. */
memset(&channel, 0, sizeof(channel)); /* zero channel */
channel.sin_family = AF_INET;
channel.sin_addr.s_addr = htonl(INADDR_ANY);
channel.sin_port = htons(SERVER_PORT);

/* Passive open. Wait for connection. */
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* create socket */
if (s < 0) fatal("socket failed");
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));

b = bind(s, (struct sockaddr *) &channel, sizeof(channel));
if (b < 0) fatal("bind failed");

l = listen(s, QUEUE_SIZE); /* specify queue size */
if (l < 0) fatal("listen failed");

/* Socket is now set up and bound. Wait for connection and process it. */
while (1) {
sa = accept(s, 0, 0); /* block for connection request */
if (sa < 0) fatal("accept failed");

read(sa, buf, BUF_SIZE); /* read file name from socket */

/* Get and return the file. */
fd = open(buf, O_RDONLY); /* open the file to be sent back */
if (fd < 0) fatal("open failed");

while (1) {
bytes = read(fd, buf, BUF_SIZE); /* read from file */
if (bytes <= 0) break; /* check for end of file */
write(sa, buf, bytes); /* write bytes to socket */
}
close(fd); /* close file */
close(sa); /* close connection */
}
}
Academic Honesty!
It is not our intention to break the school's academic policy. Posted solutions are meant to be used as a reference and should not be submitted as is. We are not held liable for any misuse of the solutions. Please see the frequently asked questions page for further questions and inquiries.
Kindly complete the form. Please provide a valid email address and we will get back to you within 24 hours. Payment is through PayPal, Buy me a Coffee or Cryptocurrency. We are a nonprofit organization however we need funds to keep this organization operating and to be able to complete our research and development projects.