views:

230

answers:

2

I'm teaching myself Python networking, and I recalled that back when I was teaching myself threading, I came across this page, so I copied the scripts, updated them for Python 3.1.1 and ran them. They worked perfectly.

Then I made a few modifications. My goal is to do something simple:

  1. The client pickles an integer and sends it to the server.
  2. The server receives the pickled integer, unpickles it, doubles it, then pickles it and sends it back to the client.
  3. The client receives the pickled (and doubled) integer, unpickles it, and outputs it.

Here's the server:

import pickle
import socket
import threading

class ClientThread(threading.Thread):
 def __init__(self, channel, details):
  self.channel = channel
  self.details = details
  threading.Thread.__init__ ( self )

 def run(self):
  print('Received connection:', self.details[0])
  request = self.channel.recv(1024)
  response = pickle.dumps(pickle.loads(request) * 2)
  self.channel.send(response)
  self.channel.close()
  print('Closed connection:', self.details [ 0 ])

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('', 2727))
server.listen(5)

while True:
 channel, details = server.accept()
 ClientThread(channel, details).start()

And here is the client:

import pickle
import socket
import threading

class ConnectionThread(threading.Thread):
 def run(self):
  client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  client.connect(('localhost', 2727))

  for x in range(10):
   client.send(pickle.dumps(x))
   print('Sent:',str(x))
   print('Received:',repr(pickle.loads(client.recv(1024))))

  client.close()

for x in range(5):
 ConnectionThread().start()

The server runs fine, and when I run the client it successfully connects and starts sending integers and receiving them back doubled as expected. However, very quickly it exceptions out:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "C:\Python30\lib\threading.py", line 507, in _bootstrap_inner
    self.run()
  File "C:\Users\Imagist\Desktop\server\client.py", line 13, in run
    print('Received:',repr(pickle.loads(client.recv(1024))))
socket.error: [Errno 10053] An established connection was aborted by the softwar
e in your host machine

The server continues to run and receives connections just fine; only the client crashes. What's causing this?

EDIT: I got the client working with the following code:

import pickle
import socket
import threading

class ConnectionThread(threading.Thread):
 def run(self):
  for x in range(10):
   client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   client.connect(('localhost', 2727))
   client.send(pickle.dumps(x))
   print('Sent:',str(x))
   print('Received:',repr(pickle.loads(client.recv(1024))))
   client.close()

for x in range(5):
 ConnectionThread().start()

However, I still don't understand what's going on. Isn't this just opening and closing the socket a bunch of times? Shouldn't there be time limitations to that (you shouldn't be able to open a socket so soon after closing it)?

A: 

That's not how you learn python networking programming. Nobody doing anything serious will ever program like that, so you're learning how to NOT do python networking programming.

  1. Sockets are too low-level, and python is a high-level language. Then, to do python network programming, in a pythonic way, you want to avoid dealing with sockets entirely, and leave that to some module or package. If you want to learn how the low-level stuff works, studying the existing implementations is essential anyway, so learning a high-level library is essential.
  2. pickle isn't really how you serialize python objects to send them down the wire. They are a potential security issue you're intoducing, since you can make a fake pickled packet that executes arbritary code when unpickled. It's better to use a safe serializing method - since there are many available, like calling str() on the integer object and int() on the other side.
  3. To serve/listen to multiple clients at the same time, don't use threading. In particular, never use the "one thread per connection" approach you seem to be using. It doesn't scale as you think. Since using threads is also error-prone and hard to debug, and bring absolutely no advantage to your end results, bringing problems instead, just try to avoid them entirely. Sockets (and thus higher level libraries as discussed in item 1) can work in a non-blocking mode, where you can operate while other code is running, and that can be done without using threads.

I've just seen your comment on the question:

@leeroy (continued) My goal is to work up to implementing something like this: http://www.mcwalter.org/technology/java/httpd/tiny/index.html only in Python

Well, here's a working implementation:

from SimpleHTTPServer import SimpleHTTPRequestHandler as RH
from SocketServer import TCPServer

print "serving current directory at port 8000"
TCPServer(("", 8000), RH).serve_forever()
nosklo
-1. I don't think there's any need for such a hostile answer, especially for someone new to Python.
dcrosta
@dcrosta: sorry but where is my answer hostile?
nosklo
@dcrosta - For the record, I'm not new to Python, just new to networking. I've been answering Python questions for a while on SO. :)
Imagist
@Imagist: did you find my answer hostile somehow?
nosklo
@nosklo A little, yes.
Imagist
A: 

Your client is now correct - you want to open the socket send the data, receive the reply and then close the socket.

The error original error was caused by the server closing the socket after it sent the first response which caused the client to receive a connection closed message when it tried to send the second message on the same connection.

However, I still don't understand what's going on. Isn't this just opening and closing the socket a bunch of times?

Yes. This is acceptable, if not the highest performance way of doing things.

Shouldn't there be time limitations to that (you shouldn't be able to open a socket so soon after closing it)?

You can open a client socket as quickly as you like as every time you open a socket you will get a new local port number, meaning that the connections won't interfere. In the server code above, it will start a new thread for each incoming connection.

There are 4 parts to every IP connection (source_address, source_port, destination_address, destination_port) and this quad (as it is known) must change for ever connection. Everything except source_port is fixed for a client socket so that is what the OS changes for you.

Opening server sockets is more troublesome - if you want to open a new server socket quickly, your

server.bind(('', 2727))

Above then you need to read up on SO_REUSEADDR.

Nick Craig-Wood