Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (104 page)

BOOK: Programming Python
5.67Mb size Format: txt, pdf, ePub
ads
Running Socket Programs Locally

Let’s put this
client and server to work. There are two ways to run these
scripts—
on either the same machine
or two different machines. To run the client and the server on the same
machine, bring up two command-line consoles on your computer, start the
server program in one, and run the client repeatedly in the other. The
server keeps running and responds to requests made each time you run the
client script in the other window.

For instance, here is the text that shows up in the MS-DOS console
window where I’ve started the server script:

C:\...\PP4E\Internet\Sockets>
python echo-server.py
Server connected by ('127.0.0.1', 57666)
Server connected by ('127.0.0.1', 57667)
Server connected by ('127.0.0.1', 57668)

The output here gives the address (machine IP name and port
number) of each connecting client. Like most servers, this one runs
perpetually, listening for client connection requests. This server
receives three, but I have to show you the client window’s text for you
to understand what this means:

C:\...\PP4E\Internet\Sockets>
python echo-client.py
Client received: b'Echo=>Hello network world'
C:\...\PP4E\Internet\Sockets>
python echo-client.py localhost spam Spam SPAM
Client received: b'Echo=>spam'
Client received: b'Echo=>Spam'
Client received: b'Echo=>SPAM'
C:\...\PP4E\Internet\Sockets>
python echo-client.py localhost Shrubbery
Client received: b'Echo=>Shrubbery'

Here, I ran the client script three times, while the server script
kept running in the other window. Each client connected to the server,
sent it a message of one or more lines of text, and read back the
server’s reply—an echo of each line of text sent from the client. And
each time a client is run, a new connection message shows up in the
server’s window (that’s why we got three). Because the server’s coded as
an infinite loop, you may need to kill it with Task Manager on Windows
when you’re done testing, because a Ctrl-C in the server’s console
window is ignored; other platforms may fare better.

It’s important to notice that client and server are running on the
same machine here (a Windows PC). The server and client agree on the
port number, but they use the machine names
""
and
localhost
, respectively, to refer to the
computer on which they are running. In fact, there is no Internet
connection to speak of. This is just IPC, of the sort we saw in
Chapter 5
: sockets also work well as
cross-program communications tools on a single machine.

Running Socket Programs Remotely

To make these
scripts talk over the Internet rather than on a single
machine and sample the broader scope of sockets, we have to do some
extra work to run the server on a different computer. First, upload the
server’s source file to a remote machine where you have an account and a
Python. Here’s how I do it with FTP to a site that hosts a domain name
of my own,
learning-python.com
;
most informational lines in the following have been removed, your server
name and upload interface details will vary, and there are other ways to
copy files to a computer (e.g., FTP client GUIs, email, web page post
forms, and so on—see
Tips on Using Remote Servers
for
hints on accessing remote servers):

C:\...\PP4E\Internet\Sockets>
ftp learning-python.com
Connected to learning-python.com.
User (learning-python.com:(none)):
xxxxxxxx
Password:
yyyyyyyy
ftp>
mkdir scripts
ftp>
cd scripts
ftp>
put echo-server.py
ftp>
quit

Once you have the server program loaded on the other computer, you
need to run it there. Connect to that computer and start the server
program. I usually Telnet or SSH into my server machine and start the
server program as a perpetually running process from the command line.
The
&
syntax in Unix/Linux shells
can be used to run the server script in the background; we could also
make the server directly executable with a
#!
line and a
chmod
command (see
Chapter 3
for details).

Here is the text that shows up in a window on my PC that is
running a SSH session with the free PuTTY client, connected to the Linux
server where my account is hosted (again, less a few deleted
informational lines):

login as:
xxxxxxxx
[email protected]'s password:
yyyyyyyy
Last login: Fri Apr 23 07:46:33 2010 from 72.236.109.185
[...]$
cd scripts
[...]$
python echo-server.py &
[1] 23016

Now that the server is listening for connections on the Net, run
the client on your local computer multiple times again. This time, the
client runs on a different machine than the server, so we pass in the
server’s domain or IP name as a client command-line argument. The server
still uses a machine name of
""
because it always listens on whatever machine it runs on. Here is what
shows up in the remote
learning-python.com
server’s SSH window on my
PC:

[...]$ Server connected by ('72.236.109.185', 57697)
Server connected by ('72.236.109.185', 57698)
Server connected by ('72.236.109.185', 57699)
Server connected by ('72.236.109.185', 57700)

And here is what appears in the Windows console window where I run
the client. A “connected by” message appears in the server SSH window
each time the client script is run in the client window:

C:\...\PP4E\Internet\Sockets>
python echo-client.py learning-python.com
Client received: b'Echo=>Hello network world'
C:\...\PP4E\Internet\Sockets>
python echo-client.py learning-python.com ni Ni NI
Client received: b'Echo=>ni'
Client received: b'Echo=>Ni'
Client received: b'Echo=>NI'
C:\...\PP4E\Internet\Sockets>
python echo-client.py learning-python.com Shrubbery
Client received: b'Echo=>Shrubbery'

The
ping
command can be used to
get an IP address for a machine’s domain name; either machine name form
can be used to connect in the client:

C:\...\PP4E\Internet\Sockets>
ping learning-python.com
Pinging learning-python.com [97.74.215.115] with 32 bytes of data:
Reply from 97.74.215.115: bytes=32 time=94ms TTL=47
Ctrl-C
C:\...\PP4E\Internet\Sockets>
python echo-client.py 97.74.215.115 Brave Sir Robin
Client received: b'Echo=>Brave'
Client received: b'Echo=>Sir'
Client received: b'Echo=>Robin'

This output is perhaps a bit understated—a lot is happening under
the hood. The client, running on my Windows laptop, connects with and
talks to the server program running on a Linux machine perhaps thousands
of miles away. It all happens about as fast as when client and server
both run on the laptop, and it uses the same library calls; only the
server name passed to clients differs.

Though simple, this illustrates one of the major advantages of
using sockets for cross-program communication: they naturally support
running the conversing programs on different machines, with little or no
change to the scripts themselves. In the process, sockets make it easy
to decouple and distribute parts of a system over a network when
needed.

Socket pragmatics

Before we move on, there are
three practical usage details you should know. First,
you can run the client and server like this on any two Internet-aware
machines where Python is installed. Of course, to run the client and
server on different computers, you need both a live Internet
connection and access to another machine on which to run the
server.

This need not be an expensive proposition, though; when sockets
are opened, Python is happy to initiate and use whatever connectivity
you have, be it a dedicated T1 line, wireless router, cable modem, or
dial-up account. Moreover, if you don’t have a server account of your
own like the one I’m using on
learning-python.com
, simply run client and
server examples on the same machine,
localhost
, as shown earlier; all you need
then is a computer that allows sockets, and most do.

Second, the socket module generally raises exceptions if you ask
for something invalid. For instance, trying to connect to a
nonexistent server (or unreachable servers, if you have no Internet
link) fails:

C:\...\PP4E\Internet\Sockets>
python echo-client.py www.nonesuch.com hello
Traceback (most recent call last):
File "echo-client.py", line 24, in
sockobj.connect((serverHost, serverPort)) # connect to server machine...
socket.error: [Errno 10060] A connection attempt failed because the connected
party did not properly respond after a period of time, or established connection
failed because connected host has failed to respond

Finally, also be sure to kill the server process before
restarting it again, or else the port number will still be in use, and
you’ll get another exception; on my remote server
machine
:

[...]$
ps -x
PID TTY STAT TIME COMMAND
5378 pts/0 S 0:00 python echo-server.py
22017 pts/0 Ss 0:00 -bash
26805 pts/0 R+ 0:00 ps –x
[...]$
python echo-server.py
Traceback (most recent call last):
File "echo-server.py", line 14, in
sockobj.bind((myHost, myPort)) # bind it to server port number
socket.error: [Errno 10048] Only one usage of each socket address (protocol/
network address/port) is normally permitted

A series of Ctrl-Cs will kill the server on Linux (be sure to
type
fg
to bring it to the
foreground first if started with an
&
):

[...]$
fg
python echo-server.py
Traceback (most recent call last):
File "echo-server.py", line 18, in
connection, address = sockobj.accept() # wait for next client connect
KeyboardInterrupt

As mentioned earlier, a Ctrl-C kill key combination won’t kill
the server on my Windows 7 machine, however. To kill the perpetually
running server process running locally on Windows, you may need to
start Task Manager (e.g., using a Ctrl-Alt-Delete key combination),
and then end the Python task by selecting it in the process listbox
that appears. Closing the window in which the server is running will
also suffice on Windows, but you’ll lose that window’s command
history. You can also usually kill a server on Linux with a
kill −9
pid
shell
command if it is running in another window or in the background, but
Ctrl-C requires less
typing.

Tips on Using Remote Servers

Some of this
chapter’s examples run server code on a remote
computer. Though you can also run the examples locally on
localhost
, remote execution better
captures the flexibility and power of sockets. To run remotely,
you’ll need access to an Internet accessible computer with Python,
where you can upload and run scripts. You’ll also need to be able to
access the remote server from your PC. To help with this last step,
here are a few hints for readers new to using remote servers.

To transfer scripts to a remote machine, the
FTP
command is standard on Windows machines and
most others. On Windows, simply type it in a console window to
connect to an FTP server or start your favorite FTP client GUI
program; on Linux, type the FTP command in an xterm window. You’ll
need to supply your account name and password to connect to a
nonanonymous FTP site. For anonymous FTP, use “anonymous” for the
username and your email address for the password.

To run scripts remotely from a command line,
Telnet
is a standard command on some Unix-like
machines, too. On Windows, it’s often run as a client GUI. For some
server machines, you’ll need to use
SSH
secure
shell rather than Telnet to access a shell prompt. There are a
variety of SSH utilities available on the Web, including PuTTY, used
for this book. Python itself comes with a
telnetlib
telnet module, and a web search
will reveals current SSH options for Python scripts, including
ssh.py
,
paramiko
,
Twisted
,
Pexpect
, and even
subprocess.Popen
.

Spawning Clients in Parallel

So far, we’ve run a
server locally and remotely, and run individual clients
manually, one after another. Realistic servers are generally intended to
handle many clients, of course, and possibly at the same time. To see
how our echo server handles the load, let’s fire up eight copies of the
client script in parallel using the script in
Example 12-3
; see the end of
Chapter 5
for details on the
launchmodes
module used here to spawn clients
and alternatives such as the
multiprocessing
and
subprocess
modules.

Example 12-3. PP4E\Internet\Sockets\testecho.py

import sys
from PP4E.launchmodes import QuietPortableLauncher
numclients = 8
def start(cmdline):
QuietPortableLauncher(cmdline, cmdline)()
# start('echo-server.py') # spawn server locally if not yet started
args = ' '.join(sys.argv[1:]) # pass server name if running remotely
for i in range(numclients):
start('echo-client.py %s' % args) # spawn 8? clients to test the server

To run this script, pass no arguments to talk to a server
listening on port 50007 on the local machine; pass a real machine name
to talk to a server running remotely. Three console windows come into
play in this scheme—the client, a local server, and a remote server. On
Windows, the clients’ output is discarded when spawned from this script,
but it would be similar to what we’ve already seen. Here’s the client
window
interaction—
8 clients are
spawned locally to talk to both a local and a remote server:

C:\...\PP4E\Internet\Sockets>
set PYTHONPATH=C:\...\dev\Examples
C:\...\PP4E\Internet\Sockets>
python testecho.py
C:\...\PP4E\Internet\Sockets>
python testecho.py learning-python.com

If the spawned clients connect to a server run locally (the first
run of the script on the client), connection messages show up in the
server’s window on the local machine:

C:\...\PP4E\Internet\Sockets>
python echo-server.py
Server connected by ('127.0.0.1', 57721)
Server connected by ('127.0.0.1', 57722)
Server connected by ('127.0.0.1', 57723)
Server connected by ('127.0.0.1', 57724)
Server connected by ('127.0.0.1', 57725)
Server connected by ('127.0.0.1', 57726)
Server connected by ('127.0.0.1', 57727)
Server connected by ('127.0.0.1', 57728)

If the server is running remotely, the client connection messages
instead appear in the window displaying the SSH (or other) connection to
the remote computer, here,
learning-python.com
:

[...]$
python echo-server.py
Server connected by ('72.236.109.185', 57729)
Server connected by ('72.236.109.185', 57730)
Server connected by ('72.236.109.185', 57731)
Server connected by ('72.236.109.185', 57732)
Server connected by ('72.236.109.185', 57733)
Server connected by ('72.236.109.185', 57734)
Server connected by ('72.236.109.185', 57735)
Server connected by ('72.236.109.185', 57736)
Preview: Denied client connections

The net effect is that our echo server converses with multiple
clients, whether running locally or remotely. Keep in mind, however,
that this works for our simple scripts only because the server doesn’t
take a long time to respond to each client’s requests—it can get back
to the top of the server script’s outer
while
loop in time to process the next
incoming client. If it could not, we would probably need to change the
server to handle each client in parallel, or some might be denied a
connection.

Technically, client connections would fail after 5 clients are
already waiting for the server’s attention, as specified in the
server’s
listen
call. To prove this
to yourself, add a
time.sleep
call
somewhere inside the echo server’s main loop in
Example 12-1
after a connection is
accepted, to simulate a long-running task (this is from file
echo-server-sleep.py
in the examples
package if you wish to experiment):

while True:                                  # listen until process killed
connection, address = sockobj.accept() # wait for next client connect
while True:
data = connection.recv(1024) # read next line on client socket
time.sleep(3) # take time to process request
...

If you then run this server and the
testecho
clients script, you’ll notice that
not all 8 clients wind up receiving a connection, because the server
is too busy to empty its pending-connections queue in time. Only 6
clients are served when I run this on Windows—one accepted initially,
and 5 in the pending-requests
listen
queue. The other two clients are
denied connections and fail.

The following shows the server and client messages produced when
the server is stalled this way, including the error messages that the
two denied clients receive. To see the clients’ messages on Windows,
you can change
testecho
to use the
StartArgs
launcher with a
/B
switch at the front of the command line
to route messages to the persistent console window (see file
testecho-messages.py
in the examples
package):

C:\...\PP4E\dev\Examples\PP4E\Internet\Sockets>
echo-server-sleep.py
Server connected by ('127.0.0.1', 59625)
Server connected by ('127.0.0.1', 59626)
Server connected by ('127.0.0.1', 59627)
Server connected by ('127.0.0.1', 59628)
Server connected by ('127.0.0.1', 59629)
Server connected by ('127.0.0.1', 59630)
C:\...\PP4E\dev\Examples\PP4E\Internet\Sockets>
testecho-messages.py
/B echo-client.py
/B echo-client.py
/B echo-client.py
/B echo-client.py
/B echo-client.py
/B echo-client.py
/B echo-client.py
/B echo-client.py
Client received: b'Echo=>Hello network world'
Traceback (most recent call last):
File "C:\...\PP4E\Internet\Sockets\echo-client.py", line 24, in
sockobj.connect((serverHost, serverPort)) # connect to server machine...
socket.error: [Errno 10061] No connection could be made because the target
machine actively refused it
Traceback (most recent call last):
File "C:\...\PP4E\Internet\Sockets\echo-client.py", line 24, in
sockobj.connect((serverHost, serverPort)) # connect to server machine...
socket.error: [Errno 10061] No connection could be made because the target
machine actively refused it
Client received: b'Echo=>Hello network world'
Client received: b'Echo=>Hello network world'
Client received: b'Echo=>Hello network world'
Client received: b'Echo=>Hello network world'
Client received: b'Echo=>Hello network world'

As you can see, with such a sleepy server, 8 clients are
spawned, but only 6 receive service, and 2 fail with exceptions.
Unless clients require very little of the server’s attention, to
handle multiple requests overlapping in time we need to somehow
service clients in parallel. We’ll see how servers can handle multiple
clients more robustly in a moment; first, though, let’s experiment
with some special
ports.

BOOK: Programming Python
5.67Mb size Format: txt, pdf, ePub
ads

Other books

Louise Rennison_Georgia Nicolson 09 by Stop in the Name of Pants!
Diviner by Bryan Davis
Limitless by Alan Glynn