Python introduction
If you haven’t Pythoned before, take a look at some notes.
Compare and Swap
We’re going to us a classic hardware synchronization example for our server.
Here’s an implementation of the compare-and-swap as a Python class.
class CompareAndSwapRegister: "CompareAndSwap class" def __init__(self): self.value = 0 def CompareAndSwap(self, oldval, newval): presval = self.value if (presval == oldval): self.value = newval return presval def __str__(self): return str(self.value)
A line oriented Compare and Swap
This little module reads a couple of numbers in a line and sends them to the compare-and-swap module.
import re def lineCAS(reg, line): pat = re.compile('\s*(\d+)\s+(\d+)\s*') mat = pat.match(line) if (mat == None): return 'Bad Syntax' else: return (str(reg.CompareAndSwap(int(mat.group(1)), int(mat.group(2)))))
To master that example you need to know something about regular expressions. Actually, to call yourself a real computer geek you have to know something about regular expressions.
- Generic regular expression
- Java regular expressions
- Python regular expressions
- Python regular expressions cheat sheet
Compare and Swap driver
This program reads lines from the terminal for swap-and-compare.
#! /usr/bin/python from CompareAndSwap import CompareAndSwapRegister from lineCAS import lineCAS def stdinCAS(): x = CompareAndSwapRegister() try: while True: line = raw_input() print lineCAS(x, line) except EOFError: pass if __name__ == "__main__": stdinCAS()
This one will be faster because it compiles the regular expression only once.
#! /usr/bin/python from CompareAndSwap import CompareAndSwapRegister import re def stdinCAS(): pat = re.compile('\s*(\d+)\s+(\d+)\s*') reg = CompareAndSwapRegister() try: while True: line = raw_input() mat = pat.match(line) if (mat == None): print 'Bad Syntax' else: print (str(reg.CompareAndSwap(int(mat.group(1)), int(mat.group(2))))) except EOFError: pass if __name__ == "__main__": stdinCAS()
Compare and Swap server — One client at a time
The Python
socket
module provides accept to the socket interface.
We’ll start with
a compare-and-swap server that will accept inputs from only
one client at a time.
I suppose the client should only send a single line.
#! /usr/bin/python from CompareAndSwap import CompareAndSwapRegister import re import socket def serverCAS(): pat = re.compile('^\s*(\d+)\s+(\d+)\s*\n$') reg = CompareAndSwapRegister() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ; s.bind(('', 22222)) s.listen(1) while True: strm, addr = s.accept() print "Connection from ", addr line = strm.recv(1000) while line: print line, mat = pat.match(line) if (mat == None): repl = 'Bad Syntax' else: repl = (str(reg.CompareAndSwap(int(mat.group(1)), int(mat.group(2))))) strm.send(repl+'\n\r') line = strm.recv(1000) strm.close() except KeyboardInterrupt: print 'Exiting' s.close() if __name__ == "__main__": serverCAS()
Compare and Swap clients
You could use nc to connect to the server, but you’d block everyone else. Here’s a little shell script that uses its command line arguments to make a request to the compare-and-swap server. It will make hold the line open for very long.
#! /usr/bin/python import socket import sys if len(sys.argv) == 4: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((sys.argv[1], 22222)) s.send(sys.argv[2] + ' ' + sys.argv[3] + '\n') resp = s.recv(1000) print resp, s.close()
This client sends incomplete lines.
#! /usr/bin/python import socket import sys if len(sys.argv) == 3: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((sys.argv[1], 22222)) s.send(sys.argv[2]) resp = s.recv(1000) print resp, s.close()
This client sends the line in sections. This might result in unpredictable output.
#! /usr/bin/python import socket import sys if len(sys.argv) == 4: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((sys.argv[1], 22222)) s.send(sys.argv[2]) s.send(' ') s.send(sys.argv[3]) s.send('\n') resp = s.recv(1000) print resp, s.close()
Problem of streams
Because TCP is stream oriented, rather than message oriented, there is a problem in reading line oriented input. This requires the programmer to write code that “looks” for the end-of-line and makes sure that complete lines are written. There are several ways to do this.
- Buffers and loops
- Use a buffer and loop to read a line
- See if the buffer contains a full line
- If not, loop on
recv
until it does
- If not, loop on
- Remove the first line from the buffer and process it
- See if the buffer contains a full line
- Use a buffer and loop to write a line
- Use a buffer and loop to read a line
- Use your API to divide the socket into two separate files
Problem of serialization
The server can only process one connection at a time. This means that slow or mean clients can effectively shut down the application.
The original multi-process solution
Try out the following Python program.
import os pid = os.fork() print 'PID = ', pid print 'Hello world'
In the original Unix solution to this problem, which works well if the two connections are independent and requires shared memory if they don’t, the following calls are used to create independent processes.
fork
dup
close
exit
wait
shmxxx
for share memory
Fortunately, much of the hard work can be put off to a super server. Today Linux uses xinetd (extended internet daemon) which is managed with special configuration files. For CGI scripts, the web server acts as a super server. RPC mechanisms, such as Java Remote Method Invocation (RMI) and Open MPI, also provide a way to start and control threads on remote computers.
The bad solution would be to is to use a non-blocking socket and write busy-wait loops to test them.
Using select
The hard one-process solution is to use a blocking call that can wait
on several input sources simultaneously.
In Unix has this can be done with two similar system calls:
select
and
poll
.
In version 1.4, the
nio
(New I/O) packages were added to Java.
This includes a Selector
class that in based on
the select
mechanism
and can provide
asynchronous I/O for network applications.>
And finally, Python has its own
select
module for this purpose.
Here’s the basic idea.
- Create lists of sockets, along with associated buffers,
where the process awaits I/O
- There are separate lists for socket input, output, and exceptions
- The rendezvous socket should be place on the input list to wait for new connections.
- Buffers are also needed for queued data
- Input characters that are the beginning of a complete packet
- Output characters that are part of a packet that has been only partially accepted by the socket
- Enter a loop the uses
select
to wait for I/O- If the rendezvous socket has an new connection
- Use
accept
to create a new communicating socket - Allocate necessary buffers for the new socket
- If the server is ready for input from the client (the usual condition), add the new socket to the select input list
- If the server is read to output to the client (unusual), add the new socket to the output select list
- Use
- If an input socket has available data
- Use
read
to add that data to the socket’s input buffer - If the buffer contains a complete message, do some work
- If this results in an output message
- Place the output message in socket’s output buffer
- Place that socket on the select output list
- If this results in an output message
- Use
- If an output socket is able to receive new data
- Send data from socket’s input buffer
- If the
send
calls sends the entire buffer- Remove the socket from the write list
- If the rendezvous socket has an new connection
And here’s some examples.
Using threads
The preferred modern solution is to use
threads.
We’ll use the
Python’s
threading
module in our example.
- Write a function that receives the information needed
to process a connection
- For example, pass the socket and other needed data
- Use the constructor
threading.Thread
with keyword arugmentst = threading.Thread(target=HandleConnection, args=(reg,strm,addr,))
- Start the thread
t.start()
- Make the code thread safe
Here are a couple of example of a compare-and-swap server that
uses Lock
s.
- Locks in the server
- Locks in the class
Due to the use of the Global Interpreter Lock (GIL) in the CPython interpreter, think hard before using threads for compute-bound computation in Python. Consider Jython in these situations.