$35
Programming Assignment
CPSC-471
Goals:
1. To understand the challenges of protocol design.
2. To discover and appreciate the challenges of developing complex, real-world network applications.
3. Make sense of real-world sockets programming APIs.
4. To utilize a sockets programming API to construct simplified FTP server and client applications.
Overview
In this assignment, you will implement (simplified) FTP server and FTP client. The client shall connect to the server and support uploading and downloading of files to/from server. Before continuing, please read the Preliminaries section, below. It will save y o u hours o f frustration.
The Preliminaries
In class we covered the basics of programming with TCP sockets. Please consult the slides covering the Application Layer, if you need a quick refresher. The purpose of this section is to list some tips which can make this assignment orders of magnitude easier and more enjoyable.
Peculiarities of send() and recv() and How to Get Them Right
Consider codes for client and server codes below.
1 # Server code
2 from s o c k e t import
3
4 # The port on which to l is ten
5 s e rve r Po r t = 12000
6
7 # Create a TCP socket
8 s e r v e r S o c k e t = s o c k e t ( AF INET ,SOCK STREAM) 9
10 # Bind the socket to the port
11 s e r v e r S o c k e t . bind ( ( ’ ’ , s e r v e r P o r t ) ) 12
13 # Start listening for incoming connections
14 s e r v e r S o c k e t . l i s t e n ( 1 )
15
16 print ”The s e r v e r i s ready to r e c e i v e ”
17
18 # The buffer to s tore the received data
19 data = ””
20
21 # Forever accept incoming connections 22 while 1 :
23 # Accept a connection ; get client ’ s socket
24 connection Socket , addr = s e r v e r S o c k e t . accept ( ) 25
26 # Receive whatever the newly connected client has to send
27 data = connection Socket . recv ( 40 ) 28
29 print data
30
31 # Close the socket
32 connection Socket . c l o se ( )
1 # Client code
2 from s o c k e t import
3
4 # Name and port number of the serve r to 5 # which want to connect .
6 serverName = ” e c s . f u l l e r t o n . edu”
7 s e rv e r Po r t = 12000 8
9 # Create a s ocke t
10 c l i e n t S o c k e t = s o ck e t ( AF INET , SOCK STREAM) 11
12 # Connect to the serve r
13 c l i e n t S o c k e t . connect ( ( serverName , s e rv e r Po r t ) ) 14
15 # A s t ring we want to send to the serve r
16 data = ”Hel l o world ! This i s a very long s t r i n g. ” 17 18 # Send that string!
19 c l i e n t S o c k e t . send ( data )
20
21 # Close the socket
22 c l i e n t S o c k e t . c l o s e ( )
In the code above, the client connects to the server and sends string "Hello world! This is a very long string.” Both codes are syntactically correct. Whenever the client connects to the server, we would expect the server to print out the 40 character string sent by the client. In practice, this may not always be the case. The potential problems are outlined below:
• Problem 1: send(data) is not guaranteed to send all bytes of the data: As we learned in class, each socket has an associated send buffer. The buffer stores application data ready to be sent off. Whenever you call the send(), behind the scenes, the operating system copies the data from the buffer in your program into the send buffer of the socket. Your operating system will then take the data in the send buffer, convert it into a TCP segment, and push the segment down the protocol stack. send(), however, will return immediately after the socket’s network buffer has been filled. This means that in the client code above, if the data string was large, (i.e. Larger than the network buffer), the send() would return before sending all bytes of data.
• Problem 2: data = connectionSocket.recv(40) is not guaranteed to receive all 40 bytes: This can happen even if the sender has sent all 40 bytes. Before delving into this problem, lets review the behavior of recv(). Function recv() will block until either a) the other side has closed their socket, or b) some data has been received.
Case a) is generally not a problem. It can be easily detected by adding line if not data: immediately after data = connectionSocket.recv(40) in the receiver (which in the above example is the server). The test will evaluate to true if the other side has closed their socket.
In Case b), the variable data will contain the received data. However, data may not be 40
bytes in size (again, even if the sender sent 40 bytes). The reasons can be as follows:
1. Not all data may arrive at the same time: Recall that Internet is a packet switched network. Big chunks of data are split into multiple packets. Some of these packets may arrive at the receiver faster than others. Hence, it is possible that 20 bytes of the string arrive at the receiver, while 20 more are still on their way. recv(), however, will return as soon as it gets the first 20 bytes.
2. recv() returns after emptying the receive buffer of the socket: as we learned in class, each socket also has a receive buffer. That buffer stores all arriving data that is ready to be retrieved by the application. recv() returns after emptying the receive buffer. Because the receive buffer may not contain all data sent by the sender when the recv() is called, recv() will return before having received the specified number of bytes.
So how do we cope with the above problems? The answer is that it is up to you, the application developer, to ensure that all bytes are sent and received. This can be done by calling send() and recv() inside the loop, until all data has been sent/received. First, lets fix our client to ensure that it sends all of the specified bytes:
1 # Clie n t code
2 from s o c k e t import
3
4 # Name and port number of the serve r to 5 # which want to connect .
6 serverName = e c s . f u l l e r t o n . e d u
7 s e rv e r Po r t = 12000
8
9 # Create a s ocke t
10 c l i e n t S o c k e t = s o c k e t ( AF INET , SOCK STREAM) 11
12 # Connect to the serve r
13 c l i e n t S o c k e t . connect ( ( serverName , s e rv e r Po r t ) ) 14
15 # A s t ring we want to send to the serve r
16 data = ” Hel l o world! This i s a very long s t r i n g. ” 17
18 # byte s Sent = 0
19
20 # Keep sending bytes until a l l bytes are sent 21 while bytes Sent != len ( data ) :
22 # Send that s t r ing !
23 bytes Sent += c l i e n t S o c k e t . send ( data [ bytes Sent : ] ) 24
25 # Close the s ocke t
26 c l i e n t S o c k e t . c l o s e ( )
We made three changes. First, we added an integer variable, bytesSent, which keeps track of how many bytes the client has sent. We then added line: while bytesSent != len(data):. This loop will repeatedly call send() until bytesSent is equal to the length of our data. Another words, this line says "keep sending until all bytes of data have been sent."
Finally, inside the loop, we have line bytesSent += clientSocket.send(data[bytesSent:]). Recall, that send() returns the number of bytes it has just sent. Hence, whenever send() sends x bytes, bytesSent will be incremented by x. The parameter, data[bytesSent:], will return all bytes that come after the first bytesSent bytes of data. This ensures that in the next iteration of the loop, we will resume sending at the offset of data where we left off.
With the client code working, lets now turn our attention to the server code. The modified code is given below:
1 # Server code
2 from s o c k e t import
3
4 # The port on which to l i s t en
5 s e rv e r Po r t = 12000
6
7 # Create a TCP s ocke t
8 s e r v e r S o c k e t = s o c k e t ( AF INET ,SOCK STREAM) 9
10 # Bind the s ocke t to the port
11 s e r v e r S o c k e t . bind ( ( ’ ’ , s e r v e r P o r t ) )
12
13 # Sta rt l i s t ening for incoming connections
14 s e r v e r S o c k e t . l i s t e n ( 1 ) 15
16 print ”The s e r v e r i s ready to r e c e i v e ” 17
18 # Forever accept incoming connections 19 while 1 :
20 # Accept a connection ; get client ’ s s ocke t
21 connection Socket , addr = s e r v e r So c k e t . accept ( )
22
23 # The temporary buffer
24 tmpBuff= ”” 25
26 while len ( data ) != 4 0 :
27 # Receive whatever the newly connected client has to send
28 tmpBuff = connectionSocket . recv (40 29
30 # The other side unexpectedly clo s e d i t ’ s s ocke t
31 if not tmpBuff :
32 break
33
34 # Save the data
35 data += tmpBuff
36
37 print data 38
39 # Close the s ocket 40 connection Socket . c l o se ( )
We made several changes. First, we added loop, while len(data) != 40:, which will spin until the size of data becomes 40. Hence, if recv() is unable to receive the expected 40 bytes after the first call, the loop will ensure that the program calls recv() again in order to receive the remaining bytes. Also, we changed line data = connectionSocket.recv(40) to tmpBuff= connectionSocket.recv(40) and added
test if not tmpBuff: which will evaluate to true if the other side unexpectedly closed it’s socket. We then added line data += tmpBuff which adds the newly received bytes to the accumulator file data buffer. These changes ensure that with every iteration the newly received bytes are appended to the end of the buffer.
At this point we have fully functioning server and client programs which do not suffer from the problems discussed above. However, there is still one important caveat: In the above code, what if the server does not know the amount of data the client will be sending?
This is often the case in the real world. The answer is that the client will have to tell the server. How? Well, this is where it is up to the programmer to decide on the types and formats of messages that the server and client will be exchanging.
One approach, for example, is to decide that all messages sent from client to server start with a 10 byte header indicating the size of the data in the message followed by the actual data. Hence, the server will always receive the first 10 bytes of data, parse them and determine the size of the data, and then use a loop as we did above, in order to receive the amount of data indicated by the header. You can see such example in the directory Assignment1SampleCodes/Python/sendfile.
Before proceeding, it is recommended that you stop and think about what you just read, experiment with Assignment1SampleCodes/Python/sendfile codes, and make sure you understand everything.
Specifications
The server shall be invoked as:
python serv.py <PORT NUMBER
<PORT NUMBER specifies the port at which ftp server accepts connection requests. For example: python serv.py 1234
The ftp client is invoked as:
cli <server machine <server port
<server machine is the domain name of the server (ecs.fullerton.edu). This will be converted into 32 bit IP address using DNS lookup. For example: python cli.py ecs.fullerton.edu 1234
Upon connecting to the server, the client prints out ftp, which allows the user to execute the following commands.
ftp get <file name (downloads file <file name from the server) ftp put <filename (uploads file <file name to the server) ftp ls (lists files on the server)
ftp quit (disconnects from the server and exits)
Use two connections for each ftp session - control and data. Control channel lasts throughout the ftp session and is used to transfer all commands (ls, get, and put) from client to server and all status/error messages from server to client. The initial channel on which the client connects to server will be the control channel. Data channel is used for data transfer. It is established and torn down for every file transfer – when the client wants to transfer data (ls, get, or put), it generates an ephemeral port to use for connection and then wait for the server to connect to the client on that port. The connection is then used to upload/download file to/from the server, and is torn down after the transfer iscomplete.
For each command/request sent from the client to server, the server prints out the message indicating SUCCESS/FAILURE of the command. At the end of each transfer, client prints the filename and number of bytes transferred.
Designing the Protocol
Before you start coding, please design an application-layer protocol that meets the above specifications.
Please submit the design along with your code. Here are some guidelines to help you get started:
• What kinds of messages will be exchanged across the control channel?
• How should the other side respond to the messages?
• What sizes/formats will the messages have?
• What message exchanges have to take place in order to setup a file transfer channel?
• How will the receiving side know when to start/stop receiving the file?
• How to avoid overflowing TCP buffers?
• You may want to use diagrams to model your protocol.
Tips
• All sample files are in directory Assignment1SampleCodes. The following files contain sample Python codes, should you choose to use this language.
– Please see sample file, ephemeral.py, illustrating how to generate an ephemeral port number. This program basically creates a socket and calls bind() in order to bind the socket to port 0; calling bind() with port 0 binds the socket to the first available port.
– Please see file cmds.py which illustrates how to run an ls command from your code and to capture its input.
– Subdirectory sendfile contains files sendfileserv.py and sendfilecli.py, illustrating how to correctly transfer data over sockets (this includes large files).
SUBMISSION GUIDELINES:
• This assignment may be completed using C++, Java, or Python.
• Please hand in your source code electronically (do not submit .o or executable code) through TITANIUM. You must make sure that this code compiles and runs correctly.
• Only one person within each group should submit.
• Write a README file (text file, do not submit a .doc file) which contains – Names and email addresses of all partners.
– The programming language you use (e.g. C++, Java, or Python) – How to execute your program.
– Anything special about your submission that we should take note of.
• Place all your files under one directory with a unique name (such as p1-[userid] for assignment 1, e.g. p1-ytian1).
• Tar the contents of this directory using the following command(Linux). tar cvf [directory_ name].tar [directory_name] E.g. tar -cvf p1-ytian1.tar p1- ytian1/
• Use TITANIUM to upload the tared file you created above.
Grading Guideline:
• Protocol design 10’
• Program compiles: 5’
• Correct get command: 25’
• Correct put command: 25’
• Correct ls command: 10’
• Correct format: 5’
• Correct use of the two connections: 15’
• README file included: 5’