Strategy for developing a web server
CS 273 (OS), Fall 2019
Carry out the following tasks, in an order consistent with the stated
prerequisites.
Note: The task labels
A, B, etc., below are for convenience in
cross-referencing, and do not themselves indicate strict ordering.
Changes:
This style indicates any content added since a task was no longer under construction.
This style indicates any content deleted since a task was no longer under construction.
Index of tasks
A. Working directories
(Prereq: submission ofproj_team.htmland creation of team shared repo by RAB)B. Git development branches
(Prereq: Task A)C. Start
server.candclient.c
(Prereq: Task B)D. Start
Makefile
(Prereq: Task C; involvesserver.c, client.c)E. Start server's
work.c
(Prereq: Task D; involvesserver.c do_work())F. Start client's
request.c
(Prereq: Task D; involvesclient.c do_request())G. Request IDs and
workdatdata structure
(Prereq: Task E; involvesserver.c work.c work.h do_work())H. Round-trip client and server
(Prereq: Task E and Task F; involveswork.c request.c do_work() do_request())I. Parse HTTP request (initial version)
(Prereq: Task B; involveswork.c Makefile)J. Incorporate parser into server
(Prereq: Task I; involveswork.c Makefile)K. Add logging (
log.c)
(Prereq: Task G; involvesserver.c work.c)L. Error checking in parser
(Prereq: Task J; involvesparse.c work.c client.c)M. Send file to client
(Prereq: Task L; involvesparse.c work.c Makefile client_tests)N. Implement 501 (Not Implemented)
(Prereq: Task L; involveswork.c Makefile)O. Implement 400 (Bad Request)
(Prereq: Task L; involveswork.c parse.c Makefile)P. Implement 404 (Not Found)
(Prereq: Task M; involveswork.c Makefile)Q. Worker thread
(Prereq: Task G; involveswork.c do_work.c do_work() workdat Makefile)R. Multiple worker threads
(Prereq: Task Q; involvesserver.c work.c main() do_work() Makefile)S. Browser test
(Prereq: Task M)Under construction:
T. Parallel threads demo (Extra feature)
(Prereq: Task R and Task H; involveswork.c wdp_array.c worker() add_wdp() check_threads())
- A. Working directories
(Prereq: submission of
proj_team.htmland creation of team shared repo by RAB) -
Start by creating a clone
~/OS/pp-threadsin your~/OSworking directory for your team's shared stogit repository.cd ~/OS git clone git@stogit.cs.stolaf.edu:os-f19/repo.git pp-threads
Here, replacerepoby your team repository name (created after you submitproj_team.htmland receive a repository-creation notice from RAB) .This will make a new subdirectory
~/OS/pp-threadsthat its own git structure:Inside the subdirectory
~/OS/pp-threads, your git operations such aspullandpushwill interact with your team'srepo.Inside
~/OSor its subdirectories other thanpp-threads, git operations would interact with your usual personal course repository.
Link to index - B. Git development branches (Prereq: Task A)
-
We recommend that you use git branches to carry out your team project work.
pushtested working code only to the master branch (e.g.,git pull origin master)Create new branches for developing new code features, and merge each development branch with
masteras those new features whenever a new incremental step in development is completed and tested.By merging frequently, you avoid lengthy and complex integration of large code modifications with other code changes your teammates (or you!) may have made recently.
Exception: Do Not Break the
masterBranch! Instead, thoroughly test that themasterbranch code works after you merge your code.
Reminder of git branch commands (reference: Git book, sections 3.1 and 3.2
git branch mybranchCreate a new branch
mybranchin your local working directory.git checkout mybranchSwitch to the branch
mybranch.Note: Perform
git checkoutoperations only when all your changes have already been recorded in commits, so you won't lose any work. You can check for any changes in your code since your last commit usinggit status
git checkout -b mybranchThis is equivalent to
git branch mybranch git checkout mybranch
Only use-bwhenmybranchdoesn't exist yet.git pull origin mybranch
git push origin mybranchUse mybranch instead of
masterwhen you want topull/pushwith that branch.Note:
Omit thepulloperation the first time only with a new branch, since that branch doesn't exist yet on stogit. Thereafter, usepullthenpushas usual.git merge mybranchMerge the changes in mybranch into the current branch. To merge your branch's changes into
master, enter these two commands:git checkout master git merge mybranch
Here is a team exercise with git development branches, for teams of 2 or 3 students referred to as Student 1, Student 2, and (if any) Student 3. Perform these steps in the indicated order.
In Student 1's
~/OS/pp-threadsworking directory, Student 1creates a simple
hello.cprogram that performs a single printf of a string including Student 1's username, and Student 1 tests that code.Student 1 then creates a commit with the new file
hello.c, and performspull/pushto commit to the shared team project respository (masterbranch).
Then in Student 2's
~/OS/pp-threadsworking directory, that Student 2 performsperforms
git pull origin masterin order to receive Student 1'shello.cCreates a new branch
stu2for modifying that filehello.cgit branch stu2 git checkout stu2
You can check your current branch usinggit branch
The asterisk * in the output fromgit branchindicates your current branch. Alternatively, you can issuegit status
which should also indicate the current branch in output.Enter
ls, and verify thathello.cexists in the branchEdit the program
hello.cto add a secondprintf()instruction, in order to print a line after the original output line. Student 2 should include their username in their added output line, then should proceed to test the modified program.Create a commit for the change to
hello.c, then performgit push origin stu2
Note: This is one time when apulldoes not precede a gitpushoperation. Here, apulloperation for the branchstu2would fail, because that branch doesn't exist on stogit yet.Now, Student 2 perform a
mergeto integrate their changes into themasterbranch.git checkout master git merge stu2 git pull origin master git push origin master
Note: Thegit mergecommand will create a new merge commit, and will start up an editor forStudent 2to document that merge commit. Exiting that editor immediately will serve our purposes here and enable themergecommit to complete.
In a 3-member team, Student 3 should carry out the same steps as Student 2, except:
add a
printf()to print one line of output before the original line created by Student 1 (include Student 3's username in the added output);The name of the branch should be
stu3.
Now Student 1 should perform
Enter the command
git pull origin master
to retrieve the changes tohello.c, and test those changes.Create a development branch
stu1, and edithello.cto add more characters to the end of the original line entered by Student 1 in that branch.Perform a git push origin stu1 operation
Merge your branch
stu1into themasterbranch, as indicated above for Student 2.
You can reuse branches or make new ones according to what makes sense to you.
Once you have successfully
merged a branch tomaster, that branch andmasterhave the sameHEAD(latest commit), so it's safe to delete that branch if you want to. Or, you could continue using that branch for your next new additions to the code.You can delete a branch using
git branch -d branchname git push origin --delete branchname
The first of these commands deletes the local branch (on your working directory), and the second command deletes the remote branch (on stogit).
Link to index - C. Start
server.candclient.c(Prereq: Task B) -
Start a development branch for your work on this task, e.g.,
git checkout -b mybranch
wheremybranchis a new branch name for your workCopy
sender.candreceiver.cfrom yourhw4directory, and rename the copiesclient.candserver.c.Modify
client.c, moving the following code frommain()into the definition of a functiondo_request()with two arguments, a socket descriptor (int) and a program name (string). Then insert a calldo_request(sd, prog)inmain()where that code used to be.char *buff = NULL; /* message buffer */ size_t bufflen = 0; /* current capacity of buff */ size_t nchars; /* number of bytes recently read */ ... printf("%d characters sent\n", ret); if ((ret = close(sd)) < 0) { printf("%s ", prog); perror("close()"); return 1; }Note: You will also need to define an integer variableretin your new functiondo_request()sinceretis needed for that code.Modify
server.c, moving the following code frommain()into the definition of a functiondo_work()with two arguments, a socket descriptor (int) and a program name (string). Then insert a calldo_work(clientd, prog)inmain()where that code used to be.char buff[MAXBUFF]; /* message buffer */ int ret; /* return value from a call */ if ((ret = recv(clientd, buff, MAXBUFF-1, 0)) < 0) { printf("%s ", prog); perror("recv()"); return 1; } buff[ret] = '\0'; // add terminating nullbyte to received array of char printf("Received message (%d chars):\n%s\n", ret, buff); if ((ret = close(clientd)) < 0) { printf("%s ", prog); perror("close(clientd)"); return 1; }Compile the two programs
server.candclient.cand test/debug them, using the same networked testing as in HW4.When your code is working, push your work to your branch of the shared repository
git add client.c server.c git commit -m "Task C - start server.c and client.c git push origin mybranch
(wheremybranchshould be replaced by the name of your development branch).
Note: We are omittingpullthis time only becausemybranchis a new branch.Now merge your correct code into
master.git checkout master git merge mybranch git pull origin master git push origin master
Link to index - D. Start
Makefile(Prereq: Task C; involvesserver.c, client.c) -
Switch to your development branch
git checkout
mybranchAdd the following
Makefileto yourpp-threadsdirectory.# Makefile for pp-threads project %.o: %.c gcc -c $*.c all: server client server: server.o gcc -o server server.o client: client.o gcc -o client client.o
Explanation:
# Makefile ...is documentation%.o: %.c gcc -c $*.c
is a default rule for compiling a C program. This could be modified later as needed.Note: The indentations above must be tab characters (which could be followed by spaces).
all: server clientis the first targetalland its prerequisitesserver client.
The first target is the default when you entermake
without specifying a target file to be made.server: server.o gcc -o server server.o
is a Makefile rule for creating the executableserver:The target is
serverThe prerequisite is
server.oThe action is
gcc -o server server.o
If you enter the command
make server
then the prerequisiteserver.owill be created first (using the default rule for compiling a C program) if necessary, then the actiongcc -o server server.owill be performed if necessary."if necessary" operations are only performed if their target is out of date with their prerequisites (or a prerequisite of a prerequsite).
For example,server.ois generated only if its (implicit) prerequisiteserver.cis newer thanserver.o; andserveris generated only if its prerequitesserver.ois newer thanserver.
Test the
Makefileas follows.Exit any editors that are using your code files
client.candserver.c, in order to prevent confusion due to the following operations.make
This should recompile the target executablesserverandclientif necessary. Most likely, it will report that they are "up to date" without any recompilation, unless you have made changes since your last compile.touch server.o make
Thetouchcommand changes the modified date of a file to the current time, but doesn't change characters or other aspects of that file.
Here,touch server.omakesserver.onewer thanserver, so themakecommand should relink to create a new version ofserver.touch server.c
Thismakeshould cause one compile (ofserver.c) and one link (of the resultingserver.o), producing a new version ofserver.touch client.o make
Should cause one link operation, producing a new version of the executableclienttouch client.c
Should cause one compile and one link.
Once your
Makefileis tested and debugged, send your changes to stogit.git add Makefile git commit -m "Task D - start Makefile" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Note: You could omitgit pull origin mybranch git push origin mybranch
but then there the branch changes would only occur in your local working directory, and would not be known to stogit.We recommend posting all of your work on branches to stogit, in case someone else on your team needs to examine those changes for a later task.
Link to index - E. Start server's
work.c(Prereq: Task D; involvesserver.c do_work()) -
We now move
do_work()fromserver.cinto a separate code modulework.cwith header filework.h, so thatserver.cfocuses on obtaining new client connections, whereaswork.chandles requests received from such clients.Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Note: The
mergeoperation above merges changes on the stogit server's version ofmaster(origin/master) with your development branch. Like allmergeoperations, we expect a merge commit.Note: Another approach to updating
mybranchinvolvesgit rebase. Avoid usinggit rebasefor updating your development branch, because it generates an invented commit history that might cause confusion. . (See the second answer to this stackoverflow article for more information.)Move the definition of the function
do_work()fromserver.cto a new filework.c. Copy the#includedirectives from the beginning ofserver.cto the beginning of the new filework.c.Create a new file
work.hwith a function declaration fordo_work(), and add a new include directive#include "work.h"
at the end of the existing#includedirectives in each source fileserver.candwork.c. Here is example code forwork.h:/* header file for work.c */ void do_work(int sock, char *progname);
Modify this file as needed to fit the definition of yourdo_work()function. For example, if your functiondo_work()returns a value, use a matching return type inwork.h.Modify
Makefileto compilework.cand link the resultingwork.owhen creating the executableserver, and to update dependencies includingwork.h. The code below shows the needed changes.# Makefile for pp-threads project %.o: %.c gcc -c $*.c all: server client server: server.o work.o gcc -o server server.o work.o server.o: server.c work.h work.o: work.c work.h client: client.o gcc -o client client.o
Notes:
In the rule for the target
server, be sure to addwork.oas both a prerequisite and as a file to be linked.We added rules for
server.oandwork.oin order to show the new prerequisitework.h.Before, we used
make's default rule, which is equivalent toserver.o: server.c
The action for these rules is to compile using the default compilation rule
%.o: %.c gcc -c $*.c
If we sometime need to compile differently for some target, we can add an action to that target's rule in order to override this default.
Now issue the command
make, and debug compilation and linking as needed. Then test run the program using the tests of Task C, and debug as needed to obtain working code.Test your updated
MakefileExit any editors using
server.c,work.c, orwork.h, to avoid confusion about modification times.make
This will most likely report that your code is "up to date."touch work.c make
This should recompilework.c, then relink to produce a newserverprogram. (Test that program.)touch server.c make
Should recompileserver.cthen relink to produce a newserver.touch work.h make
This should produce two recompiles (bothserver.candwork.c) then relink (producing anotherserverexecutable).
Make a commit of your successful changes so far.
git add work.c work.h server.c Makefile git commit -m "Task E - move do_work() to work.c, create work.h"
Enter
git log
to find the hash for the commit you want to revert to (you can abbreviate the full 40-character hash to the first few characters, as long as those few characters are different than other hashes).Enter
git reset --hard hash
where hash is the (abbreviated) hash you just found.Use
git resetwith care!git reset --hardwill probably overwrite any changes you may have made since that commithash. If there's some part of your code that you want to keep, make a copy of those files (e.g., in a new subdirectory~/OS/pp-threads/tmp) before performing thatgit resetcommand.Remove unneeded
#includedirectives from the beginning ofserver.candwork.c.For example,
work.cuses system callsrecv()andclose(), which only require the header filessys/types.h,sys/socket.h, andunistd.h.work.calso requiresstdio.hforprintf()andperror(). So, remove the following:#include <stdlib.h> #include <netinet/in.h>
Note: All the header file information above was found by googling
man recv, etc.
Verify that your code still
makes and runs correctlyOnce your changes are tested and debugged, commit and send them to stogit.
git add work.c server.c git commit -m "Task E DONE - Start server's
work.c" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Note: Making an intermediate commit like this enables you to revert to a prior (committed) version of your code at a later time. For example, suppose you inadvertently made changes after the commit above that caused bugs in the program, and that you can't figure out how to fix those bugs. Then you could revert to the commit above as follows:
Link to index - F. Start client's
request.c(Prereq: Task D; involvesclient.c do_request()) -
Similarly to Task E, we move
do_request()fromclient.cinto a separate code modulerequest.cwith header filerequest.h, so thatclient.cfocuses on connecting to the server andrequest.ctransmits requests to that server.Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Move the definition of the function
do_request()fromclient.cto a new filerequest.c. Copy the#includedirectives from the beginning ofclient.cto the beginning of the new filerequest.c.Create a new file
request.hwith a function declaration fordo_request(), and add a new include directive#include "request.h"
at the end of the existing#includedirectives in each source fileclient.candrequest.c.Use an approach similar to Task E.
Modify
Makefileto compilerequest.cand link the resultingrequest.owhen creating the executableclient, and to update dependencies includingrequest.h.Use an approach similar to Task E.
Now issue the command
make, and debug compilation and linking as needed. Then test run the program using the tests of Task C, and debug as needed to obtain working code.Test your updated
MakefileUse an approach similar to Task E.
Make a commit of your successful changes so far.
git add request.c request.h client.c Makefile git commit -m "Task F - move do_request() to request.c, create request.h"
Remove unneeded
#includedirectives from the beginning ofserver.candwork.c.Use an approach similar to Task E.
Once your changes are tested and debugged, commit and send them to stogit.
git add client.c request.c git commit -m "Task F DONE - Start client's
request.c" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - G. Request IDs and
workdatdata structure (Prereq: Task E; involvesserver.c work.c work.h do_work()) -
The required logging mechanism will use a unique request ID number. We will store this request ID in a new data structure
workdat, which will eventually hold other data associated with a particular request.Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
In
server.c, define a localintvariablerequest_idinmain(), initialized at 1. Add the value of this variable as a new first argument fordo_work()(this will require changes in all three filesserver.c, work.c, work.h).Compile and test your modified
serverwith the (unmodified)clientas in HW4.Make a commit of your successful changes so far.
git add work.c work.h server.c git commit -m "Task G - Add request_id to main() and do_work()"
In
work.c, define a data structurestruct workdatwith one integer fieldrid(for Request ID). Then, at the beginning ofdo_work(), define a local variablewdof that typestruct workdat, and assign the value of the new first argument to the fieldwd.rid. Also add a temporaryprintf()call to print the value ofwd.rid, in order to verify that the request ID value 1 was correctly stored in that field.Remake and test your modified
serverwith the (unmodified)clientas in HW4.You can remove the temporary
printf()now or at a later time.Once your changes are tested and debugged, commit and send them to stogit.
git add work.c git commit -m "Task G DONE - Request IDs and
workdatdata structure" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - H. Round-trip client and server
(Prereq: Task E and Task F; involves
work.c request.c do_work() do_request()) -
______
Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Modify
do_work()in the server'swork.cto send a reply to the client, after receiving the client's message and before shutting down the socket. For now, send a 3-character messageACKas the reply.Modify
do_request()in the client'srequest.cto receive that reply message from the server, after sending it's message and before closing its socket. Also add aprintf()to print the content of the message that was received.Compile and test your modified
serverandclientas in HW4, debugging as necessary.Once your changes are tested and debugged, commit and send them to stogit.
git add work.c request.c git commit -m "Task H DONE - Round-trip client and server" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - I. Parse HTTP request (initial version)
(Prereq: Task B; involves
work.c Makefile) -
Next, we will define two more source modules for parsing an HTTP request and for interacting with files and integrate them into the server code.
Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Copy your program
copyfile.cfrom HW8 into your project directory. Add aMakefiletarget for building the programcopyfilefromcopyfile.o(and recall thatcopyfile.ois automatically generated fromcopyfile.c. Compile the program usingmake copyfile
and run that program to verify that it correctly reads an input line and performs a file copy.Make a commit of your successful changes so far.
git add copyfile.c Makefile git commit -m "Task I - import copyfile.c"
Now create a function
parse()incopyfile.cthat generates anamestructure from the input line.parse1 arg: A string.
State change: A
struct namememory location is dynamically allocated usingmalloc(), and thatstruct nameis assigned to hold dynamically allocated copies of the tokens in arg1.Return:
struct name*, the address of that assignedstruct namememory location.Then modify
main()incopyfile.cto callparse()to process the line it reads from standard input.Note: Do not include your
getline()call inparse(), which would violate the spec above.
Issue
make copyfileto generate a new version ofcopyfile, and test/debug. The version ofcopyfileshould behave the same as the prior version did.Make a commit of your successful changes so far.
git add copyfile.c git commit -m "Task I - define parse()"
Define a function
delete_name()that deallocates astruct name, satisfying the following spec.delete_name1 arg:
struct name*, the address of a dynamically allocated and assignedstruct namememory location.State change: All dynamically allocated memory pointed to by arg1 becomes deallocated using
free(), including anytok[]elements and thestruct namememory*arg1 itself.Return: None.
Also add temporary
printf()calls near the beginning and end of the definition ofdelete_name(), so you can observe whendelete_name()becomes called.Insert call(s) of
delete_name()in thecopyfile.ccode as needed to insure that thestruct nameobject allocated byparse()is correctly deallocated, in order to avoid memory leaks. Test and debug this program with correct deallocation.Make a commit of your successful changes so far.
git add copyfile.c git commit -m "Task I - add delete_name(), for correct memory management"
Move the definitions of
parse()anddelete_nameto a new fileparse.c, and put the definition ofstruct nameplus declarations for those two files in a header fileparse.h. Add#include "parse.h"
to bothcopyfile.candparse.cin order to use the struct and the two functions in both code files.Modify
Makefiletoadd a rule for target
parse.owith prerequisitesparse.candparse.hadd a rule for target
copyfile.owith prerequisitescopyfile.candparse.hadd the object file
parse.oto the prerequisites and the action for targetcopyfile
Then compile
copyfilevia the commandmake copyfile
and verify that the complation occurs correctly. Proceed to test the resulting executablecopyfile, checking that it behaves as before.Once your changes are tested and debugged, commit and send them to stogit.
git add copyfile.c parse.c parse.h Makefile git commit -m "Task I DONE - Parse HTTP request (initial version)" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - J. Incorporate parser into server
(Prereq: Task I; involves
work.c Makefile) -
______
Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Modify
work.cto callparse()in order to extract the tokens from a client's input line. (This will involve modifying eitherdo_work()orworker(), depending on whether you have completed Task Q.) Reply to the client with the second token received from that client. ModifyMakefileaccordingly (parse.hbecomes a prerequisite forwork.o, andparse.obecomes a prerequisite and an added object file in the action for targetserver).Procede to
makeand test/debug the resulting server. The behavior should be the same as before, except that the server responds with the client's second token instead ofACK.Note that the first line of the HTTP
GETprotocol should beGET filename HTTP/1.1
If you enter that line as input for the client, then the server now parses to extract thefilename. Include that protocol in a test run of the client and serverOnce your changes are tested and debugged, commit and send them to stogit.
git add work.c Makefile git commit -m "Task J DONE - Incorporate parser into server" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - K. Add logging (
log.c) (Prereq: Task G; involvesserver.c work.c) -
The log feature described in the assignment sheet calls for writing three lines to a text file to record results of receiving and responding to a client request.
Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Start a new source file
log.cthat defines five functions:start_log(), which opens a fileserver.logfor appending and writes a line indicating the beginning of a new series of log entries.START log timestamp
wheretimestamprepresents the date and time of that call tostart_log().The
fopen()call will return aFILE*value. Store that value in a static variablelogstatic FILE *log;
defined in the filelog.cbefore the definition ofstart_log(). Here, the keywordstaticindicates thatlogis accessible only within the filelog.c.For the timestamp string, allocate a local variable
nowof typetime_tand a (local) arraytimestampof 30chars, and fill that array using the following calls to the library functions:now = time(NULL); strftime(timestamp, 30, "%a, %d %b %Y %T %Z", gmtime(&now));
Note that these library calls could return errors. In particular,strftime()returns 0 if the value that would be assigned totimestamprequires more than 30 characters, including nullbyte. Otherwise,strftime()returns the number of characters stored intimestamp, excluding nullbyte. It is wise to check for this error, since we expect the RFC 1123 format needed for this project to include exactly 29 characters, excluding nullbyte.Look up the
#includefiles needed for those library calls and add them to your filelog.c.
Do not close the log file within
start_log(). We will reuse the static variablelogfor the four functions below.The project assignment doesn't specify the line printed by
start_log()(nor the line printed byend_line()below), but these aren't prohibited, and they serve as delimiters for log output from multiple runs of your server.
start_work(), to print the first log line for a client interaction on the open log file.start_work()will need two arguments: the integerrequest_id; a thread ID (which should be the pthread ID once you implement the thread Task Q, but otherwise can be an arbitrary value like 1).Use the code above involving
time()andstrftime()to obtain the timestamp string required for the first log line.
received_protocol(), to print the second log line for a client interaction on the open log file.received_protocol()should have two arguments, an integerrequest_idand a string protocol name (GET) extracted from parsing the client's message.
sent_protocol(), to print the final log line for a client's interaction.sent_protocol()should have two arguments, an integerrequest_idand a string response code (e.g.,200 OK) that was sent to the client in response to its request.
end_log()with no arguments, which prints a messageEND log
and closes theFILE*variablelog.
Also, create a file
log.hthat contains function declarations for the five functions described above.Add
#include "log.h"
toserver.c, and callstart_log()near the beginning ofmain()withinserver.c.Also include
log.hinwork.c. Add a call ofreceived_protocol()to your functiondo_work()in an appropriate place (e.g., just after parsing).Note: Add your
received_protocol()call to your functionworker()instead if you have already implemented the thread Task Q.
Add a call of
sent_protocol()todo_work()(orworker()) just after you send your reply to the client. For now, use"ACK"for the second argument; later, replace by the actual protocol you send back to the client (e.g.,"200 OK").Modify the
Makefileto incorporate the new source files.Add a new target
log.owith prerequisiteslog.candlog.h(and empty action, since the default compilation is sufficient).The targets
sender.oandwork.oshould both now havelog.has an additional prerequisite.The target
sendershould now havelog.oas an additional prerequisite andlog.oshould be added to the linking command in the action for that target.
The
makecommand should now generate your updated server. Debug your new code as necessary, and test with the (unmodified) client.Once your changes are tested and debugged, commit and send them to stogit.
git add work.c log.c git commit -m "Task K DONE - Add logging (
log.c)" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - L. Error checking in parser
(Prereq: Task J; involves
parse.c work.c client.c) -
The project spec calls for the server to receive two lines of protocol from the client.
GET pagename HTTP/1.1 Host: hostname
After Task J, the server has the capability to receive a one-line message from the client containing at least two tokens, and to extract the second token from that message.In this step, we will check the first token in that first line of protocol, and take action if that token is not the string
"GET". We will also modify the client and server in order to communicate two lines of text, and to verify that those lines match the protocol rules above for correctGETrequests.In fact, we will modify the client to send any number of two-line messages to the server, and develop a test set of messages for verifying that all cases of protocol error checking in the server. In future tasks, we can use those test messages to check that error checking hasn't been affected by some later code modification.
Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
We will first modify the client to send multiple 2-line messages. Before building any code, we will specify the desired test messages and server responses. Perform these copy operations:
cp ~rab/os/pp2/protocol_test.in . cp ~rab/os/pp2/protocol_test.out . cp protocol_test.in protocol_test.in_all cp protocol_test.out protocol_test.out_all chmod -w protocol_test.*_all
The fileprotocol_test.incontains lines such as the following.GET mypage.html HTTP/1.1 Host: hostname # 1 GET mypage2.html HTTP/1.1 Host: rns202-1.cs.stolaf.edu # 2 GIT mypage.html HTTP/1.1 Host: hostname # 3 Get mypage.html HTTP/1.1 Host: hostname # 4 Host: hostname # 5 # 6 GET nopage.html HTTP/1.1 Host: hostname # 15 ...
Here, each section terminated by a line beginning with the#character represents a protocol message to for the client to send to the server. (The number following#will be ignored in client-server messages, but is included for human reference.)The file
protocol_test.outcontains lines such as the following.mypage.html # 1 mypage2.html # 2 Unimplemented protocol GIT # 3 Unimplemented protocol Get # 4 Unimplemented protocol Host: # 5 Invalid request # 6 Cannot open file # 15 ...
Here, each one-line section terminated by a line beginning with the#character is a response message we want the server to send back to the client when that client sends the corresponding protocol message fromprotocol_test.into that server. The numbers following#show the correspondence.Finally, the copies
protocol_test.in_allandprotocol_test.out_allwill be used to record the original versions ofprotocol_test.inandprotocol_test.outduring testing. We write-protected these with achmodcommand in order to avoid inadvertently changing these originals.As a first use of the protocol test files, delete all but the first two lines of
protocol_test.in, and all but the first line ofprotocol_test.out.protocol_test.inshould contain onlyGET pagename HTTP/1.1 Host: hostname
protocol_test.outshould contain onlypagename
Now try your (unmodified) client and server, except invoke the client using this command:
./client hostname port < protocol_test.in | diff - protocol_test.out
Here,hostnameandportare values for connecting to your running server. We expect the following results:Presumably your client still reads only one line from standard input. In this case, that line will be the first line of the current
protocol_test.in, which isGET pagename HTTP/1.1
When the server receives this (one-line) request, it will extract the second token
pagenameand send that token back to the client.The client will receive that message and print it on standard output (according to Task H).
The output will be piped to the
diffcommand, which compares toprotocol_test.out.The output from
diffshould include your client's prompt for input, but no other differences (unless your client prints a label when it prints the response message received from the server).
If you observe a different behavior, make sure you understand why, and modify your code if necessary to achieve this behavior (e.g., removing a label from client's output of a response message, so the output matches the line in
protocol_test.out).Now insert the printed form of your client's prompt for input to the beginning of your file
protocol_test.out. This should cause thediffcommand in the./clientpipeline above to print nothing, indicating an exact match of your client's output with the expected output in the modifiedprotocol_test.out.Record your protocol test files in a commit.
git add protocol_test.* git commit -m "Task J - first version of protocol testing files"
You can optionally pull/push this commit in order to submit to stogit, or you can wait until a later commit is pull/pushed. Likewise, if you modified your client, you couldaddthe modified code files before creating this commit, or wait to commit these with other code changes in a later commit for this task.Create a shell script
client_teststhat performs the client testing pipeline above. This means to create a fileclient_teststhat consists of the following:#! /bin/bash ./client $1 $2 < protocol_test.in | diff - protocol_test.out
Perform the following shell command to makeclient_testsan executable.chmod +x client_tests
Then verify that the scriptclient_testsbehaves the same as the original client pipeline above by entering the following shell command../client_tests hostname port
wherehostnameandportare the hostname and port for your (already started!) server.Notes:
The first two characters
#!of the first line of a file#! /bin/bash
mark that file as a script, assuming that file also has executable mode bit(s) (which thechmod +xcommand accomplished).This is a shell script because the first line indicates that a shell
/bin/bashshould be used for interpreting the remaining lines of the file. (You could make scripts for other languages by replacing/bin/bashby a different programming language processor.)The tokens
$1and$2in the shell script represent two command-line arguments for the script. Note: This shell script doesn't have any error checking, so be sure to supply a host and port number whenever you use./client_tests.We added a newline after the pipe character
|for readability.
Modify your client to read all lines of standard input up to end of input into a single message buffer, including newline characters within that buffer after each input line. Then, send that multiline message to the server. We expect the same response as for a one-line message, since the server currently only examines one line (assuming Task J).
Define a character array
msgfor collecting the input lines, e.g.,char msg[MAXMSG]
where the preprocessor constantMAXMSGis large enough to hold one entire multiline protocol. Or, you can use an existing array that you already use in the client for receiving the server's response. Make sure at the array size (e.g.,MAXMSGis large enough - 1000 characters should be plenty for the basic assignment.Initialize
msg[]as an empty string (nullbyte inmsg[0]). Then, for each line of input, use a library function such asstrcat()to append each new line of input tomsg[].If your client uses C's
getline()library function for input (as didsender.c), note thatgetline()copies each line's newline character (if there is one) automatically.Send the entire (multi-line)
msgstring, up to but excluding the terminating nullbyte, to the server in a single message, i.e., in a singlesend()socket call.If necessary, add a temporary
printf()towork()in the server in order to verify that the server received the entire two-line message. However, your unmodified server code may already implement the output of the request message.
Now, start your server and use
client_teststo test that server, verifying that the server receives the entire multi-line message then replies with the second token of the first line, as before.Make a commit of your successful changes so far.
git add client.c client_tests git commit -m "Task L - multi-line input in client, multi-line message to server; script client_tests"
Your client now has a loop for reading input lines and appending them to
msg[]until the end of input. Modify that loop to stop repeating if either end of input is encountered or a line beginning with#is encountered.To test that modification, add the four lines
# 1 GET mypage.html HTTP/1.1 Host: rns202-1.cs.stolaf.edu # 2
at the end ofprotocol_test.in, and test the server usingclient_tests. Your server should receive onlyGET pagename HTTP/1.1 Host: hostname
and no other lines, and the server's response to the client should be unchanged (so the pipeline should show no differences with the unmodifiedprotocol_test.out). Debug as necessary to obtain this expected behavior.Now, add an outer loop in your client to send multiple requests to the server.
The body of that outer loop should start with the client's
socket()call and end after closing the socket returned by that call (indo_request()).Exit the outer loop only when end of file is encountered (not
#).One way to manage the outer loop is to use the
do whileloop syntax, e.g.,do { if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { ... } ... } while (nchars != -1);Here, we assume thatncharsholds the return value fromgetline().Note:
ncharsis a local variable indo_request(). Pass the value ofncharsas the return value ofdo_request(), and assign it (in yourdoloop) to a variablencharsfor the guard evaluation.Also check all the return values from
do_request()to return the value ofncharsfor the most recently call ofgetline().
Consider moving your loop to read multiple lines of input into
msg[]before thesocket()call, since there's no need to make a socket and try toconnect()if there is no message to send. This might lead to an outer loop with this structure:do { /* loop to read input and assemble msg[] */ if (nchars != 1) { if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { ... } ... } } while (nchars != -1);or an equivalent structure of your choice.Also, consider moving the prompt for input outside of the outer loop. We only want that prompt for interactive use, and we would probably only enter one protocol section interactively, which would mean there only needs to be one prompt. This would avoid multiple prompts in the client's output, which makes it easier to add tests to
protocol_test.out.Add the following line to the end of your
protocol_test.outmypage.html
since we now expect the client to send two requests. (Here, we are omitting the#lines in the originalprotocol_test.out_all.)
Now test your server using
client_tests. We expect the following:First, the client should read the first protocol section from standard input, which will be
GET pagename HTTP/1.1 Host: hostname
client should not add the third line#1tomsg[], but should proceed to send only that two-line message to the server.The server should receive exactly those two lines, and should respond to the client with a one-line message
pagenameThe client should receive that one-line message and should print it.
Then, in the second iteration of its outer loop, the client should read the second protocol section from standard input, namely
GET mypage.html HTTP/1.1 Host: rns202-1.cs.stolaf.edu
Again, the client should detect the#2line and take action to stop its input loop and forward the two-line message to the server.In a second iteration of the loop in
server.c'smain(), the server should receive that second two-line message, then reply to the client with the messagemypage.html.The client should receive that reply and print it.
On the third iteration of the client's outer loop, end of input should be encountered by the client. The client should then exit the outer loop and exit, without sending anything more to the server.
Finally, the
diffin the client's pipeline should report no differences.
Make a commit of your successful changes so far.
git add client.c protocol_test.in protocol_test.out git commit -m "Task L - multiple request capability in the client"
Now that we have upgraded the client for testing, we are ready to start modifying the server to check the validity of a request from the client. First, define a
char*variablereplyat the beginning of the functiondo_work()inwork.c(or in the functionworker()if you have already completed Task Q), and assign a dynamically allocated array of 8500 bytes toreply.8500 bytes is large enough for up to 300 bytes of header and 8K of content in a reply message for the client.
Also, after parsing the client's request in
do_work()(orworker()), copy the second token toreply[](e.g., withstrcpy()), thensend()the contents ofreply[]to the client. The (dynamically allocated) arrayreply[]is large enough to hold any response message for the client in the basic assignment, not just the temporary reply message consisting of that second token. Don't forget to deallocatereplyusingfree(), e.g., just after using it tosend()the response to the client.Recompile, then test your server with
client_teststo check that you get the same results as before.For the basic assignment, the server must not only recognize correct
GETrequests and act on those, but must also detect when the first token of protocol is notGET, and respond appropriately in that case.We will use the library function
sscanf()(stringscanf) to examine that first token, and postpone full parsing of a request until we know that first token isGET.sscanf()performs input the same asscanf()does, exceptsscanf()reads from a string instead of standard input. For example, the codechar *str = "62 49"; int val; sscanf(str, "%d", &val);
assigns the value 62 to the integer variableval.In the function
do_work()(orworker()), usesscanf()with the format string"%s"to read the first string (up to the first whitespace character) from the request message received from the client into an array of characterschar first[100];
Check that the return value from
sscanf()is 1 which means that one value was assigned (namely, the value offirst[]). If that return value is not 1, then there must not be a first token in the message from the client, so copyInvalid request
toreply[]and sendreplyto the client. Also callreceived_protocol()to record that anEmpty protocolwas received from the client.Otherwise, a first token exists. Compare that first string to
"GET", e.g., usingstrcmp().If the string in
first[]matches"GET", proceed to your code for parsing the client's request and send the second token to the client as before. Callreceived_protocol()to record thatGETwas received from the client.If
first[]does not match"GET", send the stringUnimplemented protocol first
to the client, wherefirstis the string value contained infirst[]. (You could use library functions such asstrcpy()andstrcat().) Callreceived_protocol()to record thatfirstwas received from the client.
Double-check your code to insure that the dynamically allocated memory for
replyis correctly deallocated usingfree()in all cases. Also check to make sure thatreceived_protocol()is called only once per client request.
Add the test case
#3to yourprotocol_test.inandprotocol_test.outfiles.This means to add 3 lines
GIT mypage.html HTTP/1.1 Host: hostname # 3
to the end ofprotocol_test.in(these lines can be found inprotocol_test.in_all) , and add the two linesUnimplemented protocol GIT # 3
(found inprotocol_test.out_all) to the end ofprotocol_test.out.
Recompile, and test your server with
client_tests.Note: The label
#3inprotocol_test.outwill cause an extra section ofdiffoutput from the scriptclient_tests. To avoid this, add the following line to the scriptclient_tests:#! /bin/bash ./client $1 $2 < protocol_test.in | grep -v '^#' | diff - protocol_test.out
The added line inserts another command (grep) to the pipeline that removes all lines beginning with#from the output stream from./client.You may optionally add labels
#1and#2to yourprotocol_test.outfile if you wish.
Check that you see no unexpected
diffoutput or other issues. Debug as needed.Then, add the test cases
#4,#5,#6, and#7to yourprotocol_test.inandprotocol_test.outfiles.This means to add 9 lines
Get mypage.html HTTP/1.1 Host: hostname # 4 Host: hostname # 5 # 6 # 7
These lines can be found in
protocol_test.in_all.The final line
#7causes an empty request to be sent to the server) to the end ofprotocol_test.in
Also add the 8 lines
Unimplemented protocol Get # 4 Unimplemented protocol Host: # 5 Invalid request # 6 Invalid request # 7
(found inprotocol_test.out_all) to the end ofprotocol_test.out.
Recompile, and test your server with the (modified)
client_teststo check that you see no unexpecteddiffoutput or other issues. Debug as needed.Note: If you encounter a segmentation fault for one of the test cases
#6and/or#7, be sure to check the return value fromsscanf()before using the value offirst[], sincesscanf()doesn't necessarily assign a value to its variablefirstwhen the format string isn't matched.
Make a commit of your successful changes so far.
git add work.c protocol_test.in protocol_test.out client_tests git commit -m "Task L - Checking the first token in client's protocol"
The server now detects cases when a client request doesn't begin with the token
GET, and takes action in those cases. We will now proceed to take action when the first token isGET.Define a function
parse_GET()inparse.cfor validating the protocol for aGETrequest.parse_GET2 args: A
char*message received from the client, and an array ofchar.State change: A string is copied into arg2 that indicates whether or not the client message
arg1satisfies the protocol rules for an HTTPGETrequest.Return: An
intvalue 1 if arg1 is a valid HTTPGETrequest, and 0 otherwise.Note: For the remainder of this task, we will use the name
requestto refer to arg1 in this spec, and the namereplyfor arg2.Rewrite your
do_work()to callparse_GET()(or rewriteworker(), if you have already implemented Task Q), and move your code for parsing the client's request message toparse_GET().In
do_work()/worker(), pass the 8500-byte stringreplyas the first argument for the call ofparse_GET(), and pass the request message received from the client as the second argument for that call. (Thus, we're using the namereplyfor both the local variable indo_work()/worker()and for arg1 withinparse_GET().)
Your initial version of
parse_GET()should parse the stringrequest, then copy the second token toreply[](i.e.,parse_GET()'s arg2) and return 1. Also, afterdo_work()(orworker()) callsparse_GET(),do_work()/worker()should send the value in (its local variable)replyto the client as before.Within
parse_GET(), you can copy a string intoreply[]using a library function such asstrcpy().Be sure to add a declaration of
parse_GET()toparse.h, sowork.ccan compile using the new function.
Recompile, and test your server with
client_teststo check that you see no unexpecteddiffoutput or other issues. Debug as needed.Make a commit of your successful changes so far.
git add work.c parse.c parse.h git commit -m "Task L - add function parse_GET() in parse.c"
The remainder of this task involves modifying
parse_GET()to check for token-count errors represented inprotocol_test.in_alland generate the appropriate one-line responses to the client indicated inprotocol_test.out_all.Insert an
ifstatement inparse_GET()just after your code for parsing the (first line of) the argumentrequest[]into tokens, to check whether the number of tokens equals 3. If the count of tokens is not 3, copy the string"Invalid request"toreply[]and return the value 0 fromparse_GET(). Otherwise, proceed with the rest of your code forparse_GET().As before, you can copy that string into
reply[]usingstrcpy().Note: No
elseis required for thisif- you can simply proceed with the remainder of theparse_GET()code, since that code will not be reached by the CPU if you have already returned fromparse_GET()during thatifstatement.
Add test case
#8to the end of yourprotocol_test.in, i.e., the three linesGET try.txt Host: hostname # 8
and add the corresponding two linesInvalid request # 8
to the end of yourprotocol_test.out. Then recompile and test run your server againstclient_tests, and verify that thediffproduces no output, or debug until this test succeeds.Add test cases
#9and#10to your filesprotocol_test.inandprotocol_test.out.Then recompile and test run your server with
client_tests, and verify that thediffproduces no output, or debug until this test succeeds.Make a commit of your successful changes so far.
git add parse.c protocol_test.in protocol_test.out git commit -m "Task L - parse_GET() checks number of tokens in first protocol line"
Now add code to parse the second line of protocol in
parse_GET()'s argumentrequest[], using a second data structure (in order to preserve the results of parsing the first line of protocol).Add this code after the
iffor checking the number of tokens in the first protocol line, but before returning the second token.We suggest that you define a
char*pointer variablesecondpand assign address of the first element ofrequest[]after the first newline tosecondp, and parse starting atsecondpinstead of at the beginning ofrequest. (We will usesecondpbelow in order to refer to the beginning of the second protocol line withinrequest[].)Parse the second line into a separate data structure, so you can still access tokens of the first line.
Then, add a second
iffor checking the count of tokens in the second line of protocol. If that count is not 2, copy the string"Invalid request"toreply[]and return the value 0 immediately. Otherwise, proceed with the rest of your code forparse_GET()(i.e., copy the second token of the first line toreply[], deallocate the memory that was dynamically allocated for tokens, and return 1).Add test case
# 11to your filesprotocol_test.inandprotocol_test.out. Recompile and test run your server againstclient_tests, and verify that thediffproduces no output, or debug until this test succeeds.Add test cases
#12through#14to your filesprotocol_test.inandprotocol_test.out. Recompile and test run your server withclient_tests, and verify that thediffproduces no output, or debug until these tests succeed.Once your changes are tested and debugged, commit and send them to stogit.
git add parse.c protocol_test.in protocol_test.out git commit -m "Task L DONE - Error checking in parser" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - M. Send file to client
(Prereq: Task L; involves
parse.c work.c Makefile client_tests) -
We are now ready to send a requested file to the client, using the correct HTTP protocol for the response message.
Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Copy the files
mypage.html,mypage2.html, andprotocol_test.out_200to your working directory.cp ~rab/os/pp2/mypage.html . cp ~rab/os/pp2/mypage2.html . cp ~rab/os/pp2/protocol_test.out_200 .
The two.htmlfiles are examples for protocol testing, andprotocol_test.out_200has expected replies in HTTP protocol from the server to the client for the first two test requests (only) inprotocol_test.in_all.Note for Fall 2019: Also make fresh copies of
protocol_test.in_allandprotocol_test.out_allfor use in this task.chmod +w protocol_test.in_all protocol_test.out_all cp ~rab/os/pp2/protocol_test.in protocol_test.in_all cp ~rab/os/pp2/protocol_test.out protocol_test.out_all chmod -w protocol_test.in_all protocol_test.out_all
Modify
parse.candparse.hto add a functionhttp_header()that satisfies the following spec.http_header3 args: An array of characters, a
char*string representing a protocol response code (e.g."200 OK") and anintstring representing the length of content to follow the header.State change: A valid HTTP header of type arg2 with
Content-Length:value arg3 is assigned to the beginning of the array arg1, followed by a nullbyte.Return:
char *, the (modified) array arg1.For example, the call
http_header(reply, "200 OK", 425)should assign the following string to the beginning ofreply[]."HTTP/1.1 200 OK\r Content-type: text/html; charset=utf-8\r Content-length: 425\r \r "
(recall that\rrepresents the CR character, required for the ends of HTTP header lines). This HTTP header would be appropriate for sending a 425-byte file in response to a client'sGETrequest.Note: As this example shows, you may omit the
Date:andConnection:fields in HTTP reply headers for this project.Add a function declaration for
http_header()toparse.h, and add the function definition toparse.c.
Then, modify
do_work()(orworker()) to check the return value from its call ofparse_GET().If
parse_GET()returns 0 (indicating that parsing failed), thensend
reply[]to the client as before,Call sent_protocol() to record that
reply[]was sent to the client.deallocate
reply[]usingfree(), andreturn from
do_work()(orworker())
We can now assume that
parse_GET()returns 1 (successful parsing of client's request message). Carry out these steps:copy
reply[](the file name that was the second token of theGETrequest) to achararray variablefilename,call
http_header()to assign a200 OKheader toreply[]with the length offilenameas the third argument,append
filenametoreply[], andsend this revised
reply[]to the client.
To test these changes, replace the first two tests (four lines) of your
protocol_test.outby the two sections ofprotocol_test.out_200, so that your updatedprotocol_test.outbegins withHTTP/1.1 200 OKCR Content-Type: text/html; charset=utf-8CR Content-Length: 91CR CR <html> <head><title>My page</title></head> <body><p>This is mypage.html</p></body> </html> # 1 HTTP/1.1 200 OKCR Content-Type: text/html; charset=utf-8CR Content-Length: 107CR CR <html> <head><title>My page 2</title></head> <body><p>This is <code>mypage2.html</code></p></body> </html> # 2 Unimplemented protocol GIT # 3 Unimplemented protocol Get # 4 ...
whereCRrepresents a carriage return character (displays as^Min emacs).The file
protocol_test.out_200contains these revised sections#1and#2, including those carriage-return characters, so you can copy and paste from that file rather than entering manually.
Recompile, and test your server with
client_tests. This time, we expectclient_teststo produce differences: your client should printmypage.html
instead of<html> <head><title>My page</title></head> <body><p>This is mypage.html</p></body> </html>
and your client should printmypage2.html
instead of<html> <head><title>My page 2</title></head> <body><p>This is <code>mypage2.html</code></p></body> </html>
But there should be no other differences. Debug as needed.Make a commit of your successful changes so far.
git add parse.c parse.h work.c protocol_test.out protocol_test.in_all protocol_test.out_all git commit -m "Task M - add html_header(), revise protocol_test.out, and do preliminary test"
You may optionally also commit
protocol_test.out_200. However, this file won't be needed for future steps or tasks. Alternatively, you can either delete that file or add that filename to a file.gitignorein your working directory (then commit.gitignore).
Now, modify your
do_work()(orworker()) to send the contents of the filefilenameinstead of just the name of that file.First, try opening that file, e.g.,
FILE *fp = fopen(filename, "r");
Be sure to examine the value offpto determine whether there was an error opening that file. If there was an error, thenassign the string
"Cannot open file"
toreply[](replacing whatever was already there),send that message
reply[]to the client,call
sent_protocol()to record that"Cannot open file"was sentfree()the dynamic allocation ofreply[], andreturn
We can now assume that the file opened successfully. We will need the file's length in order to call
http_header(). The following code finds that length:fseek(fp, 0L, SEEK_END); int sz = ftell(fp); rewind(fp);
Here,the
fseek()call positionsfpto the end of the open file,the
ftell()call returns the integer position offp, which is the number of bytes in that file since we are positioned at the end, andthe
rewind()call repositionsfpto point to the beginning of the open file (which we will need in order to copy that file's contents intoreply[]later).
Now use your existing
http_header()call to insert an HTTP 200 header at the beginning ofreply[], except use the file lengthszdetermined above for the third argument of that call.Finally, append the file contents to
reply[]. This can be done by a call of the library functionfread(). Consulting with thefread()manual page, we use the following arguments for this call.fread()'s first argument is the array to copy into. This can be expressed using pointer arithmetic asreply + strlen(reply)
sincestrlen(reply)is the number of bytes in the HTTP 200 header now located at the beginning ofreply[].fread()'s second argument should be 1, meaning that we are reading individual bytes at a time (notints or some other larger unit).fread()'s third argument should be number of byte units to read, namely,sz.fread()'s final argument is theFILE*streamfp.
Recompile, and test your server with
client_tests. After these changes, we expect no output from the tests. Debug as necessary.Modify your call of
sent_protocol()indo_work()(orworker()) to print"200 OK"as the response code for a successful file transfer reply.Make a commit of your successful changes so far.
git add work.c git commit -m "Task M - test sending of files to client, and check for successful fopen call"
To test for
Cannot open file, add test case#15to your filesprotocol_test.inandprotocol_test.out.This request refers to a file
nopage.htmlthat is not supposed to exist. Do not create that file (or if there's some reason you need that filename, modify that test request#15to use a name that doesn't exist as a file.
Recompile and test run your server with
client_tests, and verify that thediffproduces no output, or debug until these tests succeed.Before leaving this task, we will refactor and automate our approach for creating new versions of
protocol_test.outthat have HTTP protocol instead of one-line informational messages. For this task, we modifiedprotocol_test.outmanually for cases#1and#2, but this will be too laborious and error prone for all the tests we want to make.First, we will retrieve our prior version of
protocol_test.out, just before adding HTTP 200 messages.mv protocol_test.out protocol_test.out_new.saved git log
Thegit logcommand displays a local history of commits that were made, in reverse chronological order. Identify the commit at the beginning of this task, i.e., just before the commit you made with the messageTask M - add html_header(), revise protocol_test.out, and do preliminary test
Commits have random 40-character hash names, but you only need to use the first few characters of such a name to refer to a commit in a git command - the first 5 or 6 characters would be enough (e.g.,a47xc6).Retrieve the version of
protocol_test.outfrom that commit using this command.git checkout hash -- protocol_test.out
wherehashis the abbreviated name of the commit you found. The retrieved file should start withmypage.html # 1 mypage2.html # 2 Unimplemented protocol GIT # 3 Unimplemented protocol Get # 4 ...
Now copy the script
add_httpand associated file into your working directory.cp ~rab/os/pp2/{add_http,[245]*.{html,http}} .This wildcard notation should copy the following files into your working directory:add_http 200.http 400.html 400.http 404.html 404.http 501.html 501.http
Make a commit of your successful changes so far.
git add add_http 200.http 400.html 400.http 404.html 404.http 501.html 501.http git commit -m "Task M - Add script add_http and related files"
Modify
Makefileto add a new prerequisiteprotocol_test.out_newto the default targetall:(e.g., afterclientandserver) also add the following new rule (e.g., at the end ofMakefile).protocol_test.out_new: cp protocol_test.out protocol_test.out_new
Test these changes by issuingmake. The targetsclientandservershould be up to date, but we expect the fileprotocol_test.out_newto be generated as a copy ofprotocol_test.out. Verify that these files have the same content at this point.Now add another
Makefilerule for updating the fileprotocol_test.out_new.protocol_test.out_new: protocol_test.out ./add_http 200
Also, enter the following two shell commands.chmod +x add_http touch protocol_test.out
Explanation:The
chmodcommand ensures that the scriptadd_httpwill be executable.The
touchcommand changes the "last modified" date to the current time for the fileprotocol_test.out. This will trigger theMakefilerule you just added for the targetprotocol_test.out_new.
makecommand, which should perform the command./add_http 200
to update the targetprotocol_test.out_new.We now expect
protocol_test.out_newto be the result of substituting HTTP 200 protocol sections for the first two cases#1and#2. In other words, the commandadd_http 200should perform the changes you made manually above. Verify that this is the case using the commanddiff protocol_test.out_new protocol_test.out_new.saved
There should be no differences.If you do find differences, determine whether this is due to errors in the use of the script
add_httpor errors that you made when manually substituting HTTP 200 protocol, in order to determine how to proceed.You can retest this step by using
touch protocol_test.out
to regenerate a new version ofprotocol_test.out_new.
Modify your own script
client_teststo perform itsdiffcommand usingprotocol_test.out_newinstead ofprotocol_test.out. Now test run your server withclient_tests, and verify thatclient_testsproduces no output, or debug until this test succeeds.After this check, we now have an automated way to transform
protocol_test.outinto a fileprotocol_test.out_newthat incorporates desired HTTP reply protocols, usingmakeand the provided scriptadd_http, and yourclient_testsscript can check against the HTTP reply protocols you have implemented so far.After this check, we no longer need the old manually produced file
protocol_test.out_new.saved, so you can either delete that file or add it to.gitignore.
Note: If you made any changes (e.g., in server source code) in order to make steps above work correctly, commit those changes with an appropriate commit message.
Make a commit of your successful changes so far.
git add Makefile client_tests git commit -m "Task M - Integrate add_http into Makefile and client_tests, for automated substitution of HTTP reply protocols"
Note:
We do not commit
protocol_test.out_newbecause that file is generated byMakefileandadd_http.
Add this filename to the file.gitignorein your working directory (creating a new one if necessary) so the git system will know thatprotocol_test.out_newdoesn't need to be committed if it changes.
Finally, add some documentation to your new
MakefilerulesAdd the following boldface line just before the rule for creating a default
protocol_test.out_new.# default creation of protocol_test.out_new protocol_test.out_new: cp protocol_test.out protocol_test.out_new
Add the following documentation line just before your rule for updating
protocol_test.out_new.# regenerate protocol_test.out_new with HTTP reply protocols impl to date
Once your changes are tested and debugged, commit and send them to stogit.
git add Makefile .gitignore git commit -m "Task M DONE - Send file to client" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - N. Implement 501 (Not Implemented)
(Prereq: Task L; involves
work.c Makefile) -
Your function
do_work()(orworker()) uses a library function such assscanf()to examine the first token of the client's request message. If that token isGET, your code callsparse_GET()and proceeds to attempt to send a file to the client.But if that first token is not
GET, your code sends anUnimplemented protocolmessage to the client. We will now implement the correct HTTP 501 reply to that situation.Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
For Fall 2019: Make fresh copies of
protocol_test.in_allandprotocol_test.out_allfor use in this task.chmod +w protocol_test.in_all protocol_test.out_all cp ~rab/os/pp2/protocol_test.in protocol_test.in_all cp ~rab/os/pp2/protocol_test.out protocol_test.out_all chmod -w protocol_test.in_all protocol_test.out_all
Locate the code in
do_work()(orworker()) where theUnimplemented protocol...message is constructed (inreply[]) and sent to the client.This is the case where
first[]does not equal"GET".
Instead of constructing a message
Unimplemented protocol first
(wherefirstis the non-GETfirst token your code finds), construct a two-part message consisting of a"501 Not Implemented"HTTP header followed by the contents of the file501.html.Delete or comment out the code for inserting
Unimplemented protocol firstat the beginning ofreply[].Open the file
501.htmlusingfopen().The file
501.htmlshould have been added to your team's repository in Task M. Check whether this file opens correctly usingfopen(), and if it fails to open, consult with the person who carried out that task if necessary to determine what happen and add501.htmland other files to your team's stogit repository and your own working directory.
Determine the length of
501.htmlusingfseek()andftell()as above, and callrewind()to reposition the open file back to the beginning.Call
http_header()to insert an HTTP 501 header at the beginning ofreply[], using the length of501.htmlfor the third argument of that call.Append the contents of
501.htmltoreply[]usingfread()as before.Send the resulting message
reply[]to the clientModify your call of
sent_protocol()for this case to indicate that"501 (Not implemented)"was sent to the client(Retain the
free()call for deallocating the memory that was dynamically allocated toreply[].)
Update the file
protocol_test.out_newto include the proper HTTP 501 protocol replies.Add a command
./add_http 501
to the actions for the updating rule forprotocol_test.out_new. Thus, that updating rule might now be# regenerate protocol_test.out_new with HTTP reply protocols impl to date protocol_test.out_new: protocol_test.out ./add_http 200 ./add_http 501
Enter the commands
touch protocol_test.in make
to regenerateprotocol_test.out_new.Check the updated contents of
protocol_test.out_new. For example, section#3ofprotocol_test.out_newshould now consist of the following lines:HTTP/1.1 501 Not ImplementedCR Content-Type: text/html; charset=utf-8CR Content-Length: 208CR CR <html> <head><title>501 Not Implemented</title></head> <body><p style='text-align:center; font-size:150%'>501 Not Implemented</p> <p>Sorry, this server doesn't handle that type of request.</p></body> </html> # 3
Recompile, and test run your server with
client_tests. Verify that thediffproduces no output, or debug until these tests succeed.Once your changes are tested and debugged, commit and send them to stogit.
git add work.c Makefile git commit -m "Task N DONE - Implement 501 (Not Implemented)" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - O. Implement 400 (Bad Request)
(Prereq: Task L; involves
work.c parse.c Makefile) -
After Task L, your server can handle requests that did not begin with the token
GETand requests that contained no tokens. ForGETrequests, your server checks whether the first two lines had the right numbers of tokens. We will now add further checks to insure that those tokens have the expected values, and modify the server to reply with the HTTP 400 protocol message when a client request has a valid request formatSwitch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
In
do_work()(orworker()) we check whether a client's message has no tokens. If there are no tokens, we send thereply[]valueInvalid request
to the client.Replace that message by an HTTP 400 protocol message.
Open the file
400.htmland determine its length, as before. Callrewind()to reposition the open file back to the beginning.Call
http_header()to insert an HTTP 400 header at the beginning ofreply[], using the length of400.htmlfor the third argument of that call.Append the contents of
400.htmltoreply[]usingfread()as before.Send the resulting message
reply[]to the clientModify your call of
sent_protocol()for this case to indicate that"400 (Bad request)"was sent to the client(Retain the
free()call for deallocating the memory that was dynamically allocated toreply[].)
Later in
do_work()(orworker()), your code sendsInvalid requestto the client ifparse_GET()returns 0. Also replace that message by an HTTP 400 protocol message.Update the file
protocol_test.out_newto include the proper HTTP 400 protocol replies.Add a command
./add_http 400
to the actions for the updating rule forprotocol_test.out_new.Enter the commands
touch protocol_test.in make
to regenerateprotocol_test.out_new.Check the updated contents of
protocol_test.out_new. For example, section#6ofprotocol_test.out_newshould now consist of the following lines:HTTP/1.1 400 Bad RequestCR Content-Type: text/html; charset=utf-8CR Content-Length: 195CR CR <html> <head><title>400 Bad Request</title></head> <body><p style='text-align:center; font-size:150%'>400 Bad Request</p> <p>The server received an incorrectly formed request.</p></body> </html> # 6
Recompile, and test run your server with
client_tests. Verify that thediffproduces no output, or debug until these tests succeed.Make a commit of your successful changes so far.
git add work.c Makefile git commit -m "Task O - HTTP 400 protocol replies"
Now add further tests in
parse_GET()to insure that tokens in the client's request have expected values.In
parse_GET(), after you have verified that there are three tokens in the first line of protocol received from the client, check that the third token matches the stringHTTP/1.1. If the strings don't match, return 0 fromparse_GET()To test this case, add sections
#16and#17toprotocol_test.inandprotocol_test.out(notprotocol_test.out_new).Recompile, test your server with
client_tests, and verify thatclient_testsproduces no output, or debug until this test succeeds.Make a commit of your successful changes so far.
git add parse.c git commit -m "Task O - complete checking for first line of GET protocol"
In
parse_GET(), after you have verified that there are two tokens in the second line of protocol received from the client, check that the first of those tokens matches the stringHost:. If the strings don't match, return 0 fromparse_GET()To test this case, add sections
#18through#21toprotocol_test.inandprotocol_test.out(notprotocol_test.out_new).Recompile, test your server with
client_tests, and verify thatclient_testsproduces no output, or debug until this test succeeds.
Once your changes are tested and debugged, commit and send them to stogit.
git add parse.c git commit -m "Task O DONE - Implement 400 (Bad Request)" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - P. Implement 404 (Not Found)
(Prereq: Task M; involves
work.c Makefile) -
Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
For Fall 2019: Make fresh copies of
protocol_test.in_allandprotocol_test.out_allfor use in this task.chmod +w protocol_test.in_all protocol_test.out_all cp ~rab/os/pp2/protocol_test.in protocol_test.in_all cp ~rab/os/pp2/protocol_test.out protocol_test.out_all chmod -w protocol_test.in_all protocol_test.out_all
Locate the code in
do_work()(orworker()) where theCannot open filemessage is constructed (inreply[]) and sent to the client.This occurs when a
GETrequest has successfully been received, but the indicated file does not open correctly usingfopen().
Instead of copying a message
Cannot open file
intoreply[], construct a two-part message consisting of a"404 Not Found"HTTP header followed by the contents of the file404.html.Delete or comment out the code for inserting
Cannot open fileat the beginning ofreply[].Open the file
404.htmlusingfopen().The file
404.htmlshould have been added to your team's repository in Task M.
Determine the length of
404.htmlusingfseek()andftell()as above, and callrewind()to reposition the open file back to the beginning.Call
http_header()to insert an HTTP 404 header at the beginning ofreply[], using the length of404.htmlfor the third argument of that call.Append the contents of
404.htmltoreply[]usingfread()as before.Send the resulting message
reply[]to the clientModify your call of
sent_protocol()for this case to indicate that"404 (Not implemented)"was sent to the client(Retain the
free()call for deallocating the memory that was dynamically allocated toreply[].)
Update the file
protocol_test.out_newto include the proper HTTP 404 protocol replies.Add a command
./add_http 404
to the actions for the updating rule forprotocol_test.out_new.Enter the commands
touch protocol_test.in make
to regenerateprotocol_test.out_new.Check the updated contents of
protocol_test.out_new. The section#15ofprotocol_test.out_newshould now consist of the following lines:HTTP/1.1 404 Not FoundCR Content-Type: text/html; charset=utf-8CR Content-Length: 189CR CR <html> <head><title>404 Not Found</title></head> <body><p style='text-align:center; font-size:150%'>404 Not Found</p> <p>Sorry, that page isn't available on this server.</p></body> </html> # 15
Recompile, and test run your server with
client_tests. Verify that thediffproduces no output, or debug until these tests succeed.Once your changes are tested and debugged, commit and send them to stogit.
git add work.c Makefile git commit -m "Task P DONE - Implement 404 (Not Found)" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - Q. Worker thread
(Prereq: Task G; involves
work.c do_work.c do_work() workdat Makefile) -
This step calls for executing most of the body of
do_work()in a pthread, rather than in themain()thread for theserverprocess. For now, themain()thread will simply block until that pthread to finish (by callingpthread_join()), but in Task R we will add a loop inmain()and avoid blocking themain()thread, so that multiple worker threads might execute at once (multi-threaded server).Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Move the definition of
struct workdatintowork.h(so thatstructcan be shared withserver.c).
Add a field namedtid(for Thread ID) of typepthread_ttostruct workdat, to be filled in with a new pthread's ID. (This field will be needed for logging when there are multiple threads.)
Also add#include <pthread.h>
to bothserver.candwork.c.Note: It's a good practice to
#includesystem header files (such aspthread.h) before user-defined header files (such aswork.h), because a user-defined header file might interfere with some internal naming within a system header file.
Modify the
Makefileto use the flag-pthreadfor compiling bothserver.candwork.c, and the same flag-pthreadwhen linking to produce the executableserver.Note: It's simplest to add
-pthreadto the default compiling rule (target%.o) and also to the link command forserver(i.e., the action for the targetserver).If you use other
.csource files for buildingserver, the extra compilation flag-pthreadwon't interfere with compiling those additional.cfiles.
Now, use
maketo generate a new version ofserver, and test it with (unchanged)clientin the usual way (as in HW4). The client and server should behave as they did before making these changes related tostruct workdat.Make a commit of your successful changes so far.
git add work.c work.h Makefile git commit -m "Task Q - add thread ID field tid to struct workdat"
Next, replace the
struct workdatvariablewdby a pointer variablewdpthat is dynamically allocated:struct workdat *wdp; wdp = (struct workdat *) malloc(sizeof(struct workdat));
Also replace field references such aswd.ridby pointer expressions such aswdp->rid, wherever these appear indo_work().Re
makeserverand test with the (unmodified)client, debugging as necessary.Note: If you make mistakes you can't figure out, either ask for help with debugging or, if necessary, revert to the previous commit.
Make a commit of your successful changes so far.
git add work.c git commit -m "Task Q - change workdat variable wd to a dynamically allocated pointer wdp"
Now define a function
worker()for a new pthread to execute, which performs all the code ofdo_work()after the definition and allocation ofwdpand the assignment ofdo_work()'s request ID argument towdp->rid. Since it will become the code for a pthread, your new functionworker()should have the following function prototype:void *worker(void *datp) {...Notes:You may either define
worker()beforedo_work(), or you may use a function declaration ofworker()before the definition ofdo_work(), and locate the definition ofworker()afterdo_work().Suggestion: Insert temporary
printf()calls at the beginning and end ofworker()printf("Starting worker()\n"); ... printf("Ending worker()\n");These calls could help with debugging this step and the future step when a pthread executesworker().
Also, insert a call
worker(wdp);
intodo_work()where the moved code used to be.(Note: It is not necessary to cast non-
voidpointers to thevoidpointer type in C, e.g.,(void*)is unnecessary inworker((void*)wdp);
)
Finally, the argument of
worker()is a pointerdatpof typevoid*, but we knowdatpactually refers to a (dynamically) allocatedstruct workdat*pointer. The code we moved fromdo_work()to the new functionworker()refers towdp, and replacing all those occurrences ofwdpby((struct workdat*) datp)
would make the code less readable. Therefore, define a pointer variablestruct workdat *wdp = datp; wdp->tid = pthread_self();
at the beginning ofworker(), and leave the references towdpunchanged in the remainder ofworker().
The second line here assigns the new pthread's ID to the added fieldtidof the pthread'sstruct workdatdata structure.(Note: It is not necessary to cast
voidpointers to a non-voidpointer type in C, e.g.,(struct workdat*)is unnecessary instruct workdat *wdp = (struct workdat*)datp;
This is unlike C++, wherevoidpointers must be cast when assigned to non-voidpointers.)
Compile and test these changes to the server, and debug as necessary. The tests should behave the same as they did before you implemented this
worker()step.Make a commit of your successful changes so far.
git add work.c git commit -m "Task Q - implement worker() function for a pthread, and test as a helper for do_work()"
Finally, carry out
worker()in a new pthread, created withindo_work. This means replacing the existing call ofworker()bypthread_t thread_id; pthread_create(&thread_id, NULL, worker, wdp);
Just after creating this thread, perform apthread_join()operation onthread_idto blockdo_work()until that thread completes its work.Then, after the thread has terminated (i.e., after calling
pthread_join()), we should deallocate the memory that was dynamically allocated for the pointerwdp, in order to avoid a "memory leak".free(wdp);
Compile and test these changes, and debug as necessary. The
server/clienttests should behave the same as they did before you implemented this step.Once your changes are tested and debugged, commit and send them to stogit.
git add work.c git commit -m "Task Q DONE - Worker thread" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - R. Multiple worker threads
(Prereq: Task Q; involves
server.c work.c main() do_work() Makefile) -
After Task Q, the work of handling a client interaction takes place within a pthread that executes
worker()instead of the server's main thread as it callsdo_work(). We now want to modifymain()(inserver.c) to enclose the call ofdo_work()in a loop, in order to handle multiple client requests, using parallel pthreads.Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
The
main()inserver.ccurrently performs oneaccept()of a client connection, then callsdo_work()to handle that connection. Enclose those calls ofaccept()anddo_work()(plus theprintf()that precedes that call ofaccept()) in an infinite loopwhile (1) { ... }Test that this modification accepts multipleclientrequests and handles them correctly.Note: You may use CTRL/C in the
serverwindow to exit this infinite loop when you want to terminate your server. (It is an extra feature to quit the server more gracefully.)
Make a commit of your successful changes so far.
git add server.c git commit -m "Task R - multiple client requests"
The server now executes each client request in a separate pthread, but those pthreads run sequentially (without parallelism) because
do_work()(which runs in the server's main thread) blocks by callingpthread_join()on the pthread it creates. To achieve parallel execution of the pthreads, we will move thepthread_join()calls fromdo_work()tomain()and make those join calls non-blocking. This way, the loop inmain()can go on toaccept()new client connections while prior client requests may still be in progress.But this means we need a data structure to hold IDs for pthreads that haven't been joined yet. We also need to
free()thestruct workdatadata structures that were dynamically allocated indo_work()(by the server's main thread), in order to avoid memory leaks (see the pthread creation step above)We will define this data structure as an array of pointers to
struct workdat, since those structs need to be deallocated, and since they also hold the thread id's that must be joined in theirtidfield.To start on this agenda, we will first build the array of pointers to
struct workdatand partially test it withinwork.c, as follows.Create a new source file
wdp_array.cthat definesa preprocessor constant for an array size
#define MAX_ARRAY 100
a
staticarray of pointers tostruct workdatstatic struct workdat *wdp_array[MAX_ARRAY];
Here, the keywordstaticindicates thatwdp_array[]is accessible only within the filewdp_array.c. Since the arraywdp_array[]is allocated globally (within the filewdp_array.c), the C language initializes all its elements as 0.A function
add_wdp()that satisfies the following spec.add_wdp1 arg: type
struct workdat*, representing the address of the data structure for a created pthread.State change: For the lowest index
isuch thatwdp_array[i]is 0, assign arg1 to that elementwdp_array[i].
If allMAX_ARRAYelements ofwdp_arrayare non-zero, then print an error message and call_exit(1)to quit the server immediately.Return: none
A function
check_threads()that satisfies the following spec.check_threads0 args.
State change: For all non-zero elements of the array
wdp_array[], callpthread_tryjoin_np()on that element'stidfield. If that call returnsEBUSY, take no action; otherwise, deallocate that element'sstruct workdatusingfree(), then assign 0 to that element.Return: none
Add
#includedirectives at the beginning ofwdp_array.cfor the system include files needed for functions used in your code, plus#include "work.h"
in order to define the data typestruct workdat.Notes:
A straightforward implementation is sufficient for the basic assignment. For example,
add_wdp()could use a simple loop to iterate until an element ofwdp_array[]containing 0 is discovered (or allMAX_ARRAYelements have been checked).However, implement thread-safe versions of your functions
add_wdp()andcheck_threads(). In the end,main()will callcheck_threads(), and each pthread will need to calladd_wdp()for its ownstruct workdat(in order to get the timing correct for that call).You can use a
pthread_mutex_tlock variable as inpthreads.cin order to achieve mutual exclusion among calls of the functionsadd_wdp()andcheck_threads(). Be sure to use a single lock variable in both functions -- we want to use the same lock variable, since both functions are interacting with the same shared data structurewdp_array[].
Also create a header file
wdp_array.hthat consists only of function declarations foradd_wdp()andcheck_threads().Add a
Makefiletarget forwdp_array.oin order to specify the prerequisiteswdp_array.c wdp_array.h work.h. Try compiling your new file withmake wdp_array.c
and debug whatever syntax issues arise.Now temporarily modify
check_threads()to comment out the call ofpthreads_tryjoin_np()and replace it by a call ofpthreads_join().Add an
#includedirective forwdp_array.htowork.c.
Add the calladd_wdp(wdp);
just after assigningpthread_self()towdp->tidinworker().
Replace the calls ofpthread_join()andfree()withindo_work()bycheck_threads();
Proceed to
makethe server, then test the server and (unmodified) client, debugging as necessary. The system should behave as before (in part becausepthread_join()is being used instead ofpthread_tryjoin_np()).
Make a commit of your successful changes so far.
git add wdp_array.c wdp_array.h work.c Makefile git commit -m "Task R - create wdp_array[], with preliminary test within work.c"
Now, move thread checking to
main()as follows.Move the call of
check_threads()fromdo_work()(inwork.c)tomain()(inserver.c), at the end of the infinite loop inmain(). This will allow a call ofdo_work()to return without waiting for its pthread to terminate.Also modify
check_threads()(inwdp_array.c) to remove the temporary call ofpthread_join()and restore the original call ofpthread_tryjoin_np().Add
#include "wdp_array.h"
toserver.c, and add that dependency to theMakefiletarget forserver.o.
Also addwdp_array.oas both a dependency for the executable targetserverand in the link action for that target.Proceed to
makethe server and test with the client. This test should behave as before.
Once your changes are tested and debugged, commit and send them to stogit.
git add server.c work.c wdp_array.c Makefile git commit -m "Task R DONE - Multiple worker threads" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index - S. Browser test (Prereq: Task M)
-
In this task, use an ordinary web brower (such as Chrome) to request and receive a web page from your
server.This is the final task for the complete basic assignment.
Link to index - T. Parallel threads demo (Extra feature)
(Prereq: Task R and Task H; involves
work.c wdp_array.c worker() add_wdp() check_threads()) -
This task is an extra feature. In this task, demonstrate that your worker threads can actually execute in parallel.
Switch to your development branch
git checkout mybranch
If there may have been updates tomastersince you last usedmybranch(e.g., updates by a team member), then updatemybranchas follows.git merge origin/master git push mybranch
Add an artificial delay in
worker(), e.g.,sleep(30);
Be sure to google thesleep()library function and make sure thatwork.chas the required#includedirectives in order to make this call.Locate this artificial
sleep()call at about the point in the code whereworker()carries out a client's request.If your team has completed Task K (
log.c), insert thesleep()call between the second and third log messages.Otherwise, add temporary
printf()calls just before and just after your code inworker()that sends a response message to the client, and add thesleep()call between those temporaryprintf()calls.These temporary
printf()messages should include the request IDwdp->rid, the thread IDwdp->tid(cast as anint), and whether thatprintf()is before or after sending the message to the client.
These temporary
printf()calls can be removed when your team completes Task K.Also add temporary
printf()calls in the functionsadd_wdp()andcheck_threads()inwdp_array.c.______
______
______
______
______
______
______
______
______
______
____________
Use
maketo generate a new version ofserverwith the artificial delay. Test thisserverwith a singleclientover the network - it should satisfy the request as usual, except for the long delay.______
______
______
______
______
______
______
______
______
______
______
______
______
Once your changes are tested and debugged, commit and send them to stogit.
git add work.c git commit -m "Task T DONE - Parallel threads demo (Extra feature)" git pull origin mybranch git push origin mybranch git checkout master git merge mybranch git pull origin master git push origin master
Link to index