Threading and Sockets in Python
Threading and Sockets in Python
First off, all code within this article is my own work, and while I encourage you to use this program and it's parts, if you use big chunks, please credit me. Also, because HBH has no TAB support (ugh) i've just left this unformatted. for functional code go to the URL at the bottom.
I'm assuming that you have a moderate background in python.
Now, sockets and threading, two things that are a genuine pain in the arse, until you get to know them. A brief overview:
SOCKETS:
These are the primary way that data is transmitted over an IP connection. You create an open socket on one machine, which opens a port (always use at least 5 digits to avoid overlap), and can then be connected to by another machine. From here data sent by the machine that connects is buffered and recieved by the machine withg the open socket. You can have 1 connection per port, remember that the port of the connecting socket must match the recieving one.
THREADING:
Threading is a way of having a computer do more than one thing at once. It allows you to set a task running, and then have the computer go off and do something else while still performing the first task. Used in games, to facilitate AI, and look for input from player among many other things. In a low level language like C++ it can often be just as efficient to write a well constructed sequence of checks, rather than threads, but in an interpreted language like python the change is speed is generally quite dramatic.
Now onto the Python side of things.
First sockets. #begin sample socket client code (this is the bit to send messages)
import socket #needed to use sockets from socket import * # we need everything class SockClient(object): #we're making a class for our client def init(self, host, port): #the init() method is run whenever a class is called self.host = host #making the inputs native to the class, so we can use them repeatedly. self.port = port #for this example it's not actually necessary, but a good habit to get into self.addr = (self.host, self.port)
sock = None
for res in getaddrinfo(self.host, self.port, AF_UNSPEC, SOCK_STREAM): #this outputs a list with the details about the coket type we\'ll be using. just trust me here, to go into more detail is way beyond the scope of this article
af, socktype, proto, canonname, sa = res #using the for loop is the easiest way to extract the data from the list
try:
sock = socket(af, socktype, proto) #creating a socket, passing on the data we just collected
except error, msg: #error handling, means we get a nice(ish) message if something goes wrong
sock = None
continue
try:
sock.connect(sa) #try to connect to our server, sa is a tuple with our reformatted host and port data
except error, msg: #more error handling
sock.close()
sock = None
continue
break
if sock is None:
raise RuntimeError, \"could not connect socket\"
self.sock = sock #finally makes the socket callable
def remotemsg(self,msg): # this function sends a message, and takes one input
self.sock.send(msg)
del msg
def close(self): # a funtion to cleanly kill the socket
self.sock.shutdown(SHUT_RDWR)
self.sock.close()
if you followed this right, and run it in a terminal, you now have a chat client. but wait nothing to connect to….
so
#begin sample socket server code
import socket from socket import *
class SockServer(object): def init(self, port): self.port = port #again, we define a class, and make all the inputs local to it self.host = '' self.addr = (self.host, self.port) self.buf = 1024 # this variable will tell it how many characters to go looking for when it finds a message
sock = None
for res in getaddrinfo(self.host, self.port, AF_UNSPEC, SOCK_STREAM, 0, AI_PASSIVE): #again, we get the addressinfo, but the 0 and AI_passive tags tell it that this will be recieving, not sending
af, socktype, proto, canonname, sa = res
try:
sock = socket(af, socktype, proto)
except error, msg:
sock = None #again, error handling
continue
try:
sock.bind(sa) # here it is binding it to a local port, so that it can listen to the outside world
sock.listen(1) # listening for connection
except error, msg:
sock.close()
sock = None
continue
break
if sock is None:
raise RuntimeError, \"could not open socket\"
self.sock,addr = sock.accept() #accept the first connection it finds
self.mainloop()
def mainloop(self):
while 1: #endless while loop
data = self.sock.recv(self.buf) #see if the other user has sent us anything
if not data: # if data is empty, they\'ve left, so we should too
print \"Remote user has terminated the session.\"
break
else:
print \'remote_user:\',data #otherwise, show us what they wrote!
print \"message?:\"
del data
self.close()
def close(self):
self.sock.shutdown(SHUT_RDWR) #again, a clean kill
self.sock.close()
so now if we run these on diffent machines, and open the server first, then the client, we can send messages one way.
but we want to chat, which means we need threading.
threading incorporates the threading module, and jsut means that the thread will do what it's doing in the background. to illustrate this i'll write a thread that adds a number to a variable 'a' whenever 'a' has a value #begin threading example import threading #so we can use threads
class OurThread(threading.Thread): #this isn't an object, it's a thread so we tell it def init(self, factor): # we want to take input, so we re-write the threading init() function self.factor = factor #make it local threading.Thread.init(self) #we still need the threading init() function def run(self): global a #tell it to look for any instance of a variable a while 1: if a != None: a +=self.factor # if a exists add 2, print it, and then make it None again print a a = None
now if we execute OurThread(2).start() and then type a=2 it will print 4 this is very handy, and a very simple example, but will tell you enough to play around.
now a full blown example:
import threading import socket #import the stuff we need from socket import *
class SockClient(object): def init(self, host, port): self.host = host self.port = port self.addr = (self.host, self.port)
sock = None
for res in getaddrinfo(self.host, self.port, AF_UNSPEC, SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
sock = socket(af, socktype, proto)
except error, msg:
sock = None
continue
try:
sock.connect(sa) #the same as our old client
except error, msg:
sock.close()
sock = None
continue
break
if sock is None:
raise RuntimeError, \"could not connect socket\"
self.sock = sock
def remotemsg(self,msg):
self.sock.send(msg)
del msg
def close(self):
self.sock.shutdown(SHUT_RDWR)
self.sock.close()
class SockServer(threading.Thread): def init(self, port): self.port = port threading.Thread.init(self) def run(self): self.host = '' self.addr = (self.host, self.port) self.buf = 1024
sock = None
for res in getaddrinfo(self.host, self.port, AF_UNSPEC, SOCK_STREAM, 0, AI_PASSIVE):
af, socktype, proto, canonname, sa = res
try:
sock = socket(af, socktype, proto)
except error, msg:
sock = None
continue
try:
sock.bind(sa) #the same as our old server, but implemented as a thread
sock.listen(1)
except error, msg:
sock.close()
sock = None
continue
break
if sock is None:
raise RuntimeError, \"could not open socket\"
self.sock,addr = sock.accept()
self.mainloop()
def mainloop(self):
while 1:
data = self.sock.recv(self.buf)
if not data:
print \"Remote user has terminated the session.\"
break
else:
print \'remote_user:\',data
print \"message?:\"
del data
self.close()
def close(self):
self.sock.shutdown(SHUT_RDWR)
self.sock.close()
send_ip = raw_input('What is other ip?') send_port = input('What is other port?') open_port = input('What is your open port?') #finally a system for managing input and output SockServer(open_port).start() fake = raw_input('Press return when you both have this message.') insock = SockClient(send_ip, send_port) while 1: print 'Message?:' msg = raw_input() if msg: insock.remotemsg(msg)
try this code, it works well. I've uploaded this article at www.freewebs.com/richohealey/python/threadart.txt and just the code segments at www.freewebs.com/richohealey/python/threadcode.txt you just have to chop it up into the indivdual programs and save as something.py the uncommented funtional chat code is at www.freewebs.com/richohealey/python/chat.py
thanks for reading.
-RichoHealey
richohealey 18 years ago
yeah. good language. i realise that this is pretty advanced, but i figure sooner or later someone wil benifit.
ghost 18 years ago
Wow… I'm actually working on something just like this at the moment (although much more simple)…
Thanks alot, this will be a really helpful guideline.