Skip to content

Commit 52097f7

Browse files
committed
moving four basic sessions to front of the course
1 parent 3a714ff commit 52097f7

31 files changed

+244
-0
lines changed

resources/session01/echo_client.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import socket
2+
import sys
3+
4+
5+
def client(msg, log_buffer=sys.stderr):
6+
server_address = ('localhost', 10000)
7+
# TODO: Replace the following line with your code which will instantiate
8+
# a TCP socket with IPv4 Addressing, call the socket you make 'sock'
9+
sock = None
10+
print('connecting to {0} port {1}'.format(*server_address), file=log_buffer)
11+
# TODO: connect your socket to the server here.
12+
13+
# you can use this as a place to accumulate the entire message echoed back
14+
# from the server
15+
received_message = ''
16+
17+
# this try/finally block exists purely to allow us to close the socket
18+
# when we are finished with it
19+
try:
20+
print('sending "{0}"'.format(msg), file=log_buffer)
21+
# TODO: send your message to the server here.
22+
23+
# TODO: the server should be sending you back your message as a series
24+
# of 16-byte chunks. Accumulate the chunks you get to build the
25+
# entire reply from the server. Make sure that you have received
26+
# the entire message and then you can break the loop.
27+
#
28+
# Log each chunk you receive. Use the print statement below to
29+
# do it. This will help in debugging problems
30+
chunk = ''
31+
print('received "{0}"'.format(chunk.decode('utf8')), file=log_buffer)
32+
finally:
33+
# TODO: after you break out of the loop receiving echoed chunks from
34+
# the server you will want to close your client socket.
35+
print('closing socket', file=log_buffer)
36+
37+
# TODO: when all is said and done, you should return the reply you got
38+
# from the server as the value of this function.
39+
40+
41+
if __name__ == '__main__':
42+
if len(sys.argv) != 2:
43+
usage = '\nusage: python echo_client.py "this is my message"\n'
44+
print(usage, file=sys.stderr)
45+
sys.exit(1)
46+
47+
msg = sys.argv[1]
48+
client(msg)

resources/session01/echo_server.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import socket
2+
import sys
3+
4+
5+
def server(log_buffer=sys.stderr):
6+
# set an address for our server
7+
address = ('127.0.0.1', 10000)
8+
# TODO: Replace the following line with your code which will instantiate
9+
# a TCP socket with IPv4 Addressing, call the socket you make 'sock'
10+
sock = None
11+
# TODO: You may find that if you repeatedly run the server script it fails,
12+
# claiming that the port is already used. You can set an option on
13+
# your socket that will fix this problem. We DID NOT talk about this
14+
# in class. Find the correct option by reading the very end of the
15+
# socket library documentation:
16+
# http://docs.python.org/3/library/socket.html#example
17+
18+
# log that we are building a server
19+
print("making a server on {0}:{1}".format(*address), file=log_buffer)
20+
21+
# TODO: bind your new sock 'sock' to the address above and begin to listen
22+
# for incoming connections
23+
24+
try:
25+
# the outer loop controls the creation of new connection sockets. The
26+
# server will handle each incoming connection one at a time.
27+
while True:
28+
print('waiting for a connection', file=log_buffer)
29+
30+
# TODO: make a new socket when a client connects, call it 'conn',
31+
# at the same time you should be able to get the address of
32+
# the client so we can report it below. Replace the
33+
# following line with your code. It is only here to prevent
34+
# syntax errors
35+
addr = ('bar', 'baz')
36+
try:
37+
print('connection - {0}:{1}'.format(*addr), file=log_buffer)
38+
39+
# the inner loop will receive messages sent by the client in
40+
# buffers. When a complete message has been received, the
41+
# loop will exit
42+
while True:
43+
# TODO: receive 16 bytes of data from the client. Store
44+
# the data you receive as 'data'. Replace the
45+
# following line with your code. It's only here as
46+
# a placeholder to prevent an error in string
47+
# formatting
48+
data = b''
49+
print('received "{0}"'.format(data.decode('utf8')))
50+
# TODO: Send the data you received back to the client, log
51+
# the fact using the print statement here. It will help in
52+
# debugging problems.
53+
print('sent "{0}"'.format(data.decode('utf8')))
54+
# TODO: Check here to see if the message you've received is
55+
# complete. If it is, break out of this inner loop.
56+
57+
finally:
58+
# TODO: When the inner loop exits, this 'finally' clause will
59+
# be hit. Use that opportunity to close the socket you
60+
# created above when a client connected.
61+
print(
62+
'echo complete, client connection closed', file=log_buffer
63+
)
64+
65+
except KeyboardInterrupt:
66+
# TODO: Use the python KeyboardInterrupt exception as a signal to
67+
# close the server socket and exit from the server function.
68+
# Replace the call to `pass` below, which is only there to
69+
# prevent syntax problems
70+
pass
71+
print('quitting echo server', file=log_buffer)
72+
73+
74+
if __name__ == '__main__':
75+
server()
76+
sys.exit(0)

resources/session01/socket_tools.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import socket
2+
3+
4+
def get_constants(prefix):
5+
return {getattr(socket, n): n for n in dir(socket) if n.startswith(prefix)}
6+
7+
8+
families = get_constants('AF_')
9+
types = get_constants('SOCK_')
10+
protocols = get_constants('IPPROTO_')
11+
12+
13+
def get_address_info(host, port):
14+
for response in socket.getaddrinfo(host, port):
15+
fam, typ, pro, nam, add = response
16+
print('family: {}'.format(families[fam]))
17+
print('type: {}'.format(types[typ]))
18+
print('protocol: {}'.format(protocols[pro]))
19+
print('canonical name: {}'.format(nam))
20+
print('socket address: {}'.format(add))
21+
print

resources/session01/tasks.txt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Session 4 Homework
2+
==================
3+
4+
Required Tasks:
5+
---------------
6+
7+
* Complete the code in ``echo_server.py`` to create a server that sends back
8+
whatever messages it receives from a client
9+
10+
* Complete the code in ``echo_client.py`` to create a client function that
11+
can send a message and receive a reply.
12+
13+
* Ensure that the tests in ``tests.py`` pass.
14+
15+
To run the tests:
16+
17+
* Open one terminal while in this folder and execute this command:
18+
19+
$ python echo_server.py
20+
21+
* Open a second terminal in this same folder and execute this command:
22+
23+
$ python tests.py
24+
25+
26+
27+
28+
Optional Tasks:
29+
---------------
30+
31+
Simple:
32+
33+
* Write a python function that lists the services provided by a given range of
34+
ports.
35+
36+
* accept the lower and upper bounds as arguments
37+
* provide sensible defaults
38+
* Ensure that it only accepts valid port numbers (0-65535)
39+
40+
Challenging:
41+
42+
* The echo server as outlined will only process a connection from one client
43+
at a time. If a second client were to attempt a connection, it would have to
44+
wait until the first message was fully echoed before it could be dealt with.
45+
46+
Python provides a module called `select` that allows waiting for I/O events
47+
in order to control flow. The `select.select` method can be used to allow
48+
our echo server to handle more than one incoming connection in "parallel".
49+
50+
Read the documentation about the `select` module
51+
(http://docs.python.org/2/library/select.html) and attempt to write a second
52+
version of the echo server that can handle multiple client connections in
53+
"parallel". You do not need to invoke threading of any kind to do this.

resources/session01/tests.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from echo_client import client
2+
import socket
3+
import unittest
4+
5+
6+
class EchoTestCase(unittest.TestCase):
7+
"""tests for the echo server and client"""
8+
9+
def send_message(self, message):
10+
"""Attempt to send a message using the client
11+
12+
In case of a socket error, fail and report the problem
13+
"""
14+
try:
15+
reply = client(message)
16+
except socket.error as e:
17+
if e.errno == 61:
18+
msg = "Error: {0}, is the server running?"
19+
self.fail(msg.format(e.strerror))
20+
else:
21+
self.fail("Unexpected Error: {0}".format(str(e)))
22+
return reply
23+
24+
def test_short_message_echo(self):
25+
"""test that a message short than 16 bytes echoes cleanly"""
26+
expected = "short message"
27+
actual = self.send_message(expected)
28+
self.assertEqual(
29+
expected,
30+
actual,
31+
"expected {0}, got {1}".format(expected, actual)
32+
)
33+
34+
def test_long_message_echo(self):
35+
"""test that a message longer than 16 bytes echoes in 16-byte chunks"""
36+
expected = "Four score and seven years ago our fathers did stuff"
37+
actual = self.send_message(expected)
38+
self.assertEqual(
39+
expected,
40+
actual,
41+
"expected {0}, got {1}".format(expected, actual)
42+
)
43+
44+
45+
if __name__ == '__main__':
46+
unittest.main()

0 commit comments

Comments
 (0)