Tutorial: Writing a TCP server in Python
During the last 12 hours of the hackathon I decided to write a TCP server for an old project I want to finally finish. I decided to write it in Python, mostly because my friend Adam likes Python and Adam would inevitably be the one answering my questions when I got stuck. I should mention that prior to yesterday evening I knew nothing about socket programing. And I only had a vague idea of what threading was.
Since not everyone has friends like Adam, I’m writing up my findings in a tutorial.
Note: A bug in my CSS is causing the code blocks to show up extra wide. I’ll fix it once I’m back home from the hackathon
First, I’m going to assume you understand that this is not a tutorial about writing an HTTP server. Instead this server will take connections from clients and keep them open to pass data back and forth until one side decides to close the connection. By keeping the connection open we eliminate the need to constantly poll the server for updates.
Socket Programming HOWTO provides a broad overview of sockets and is a good starting place.
Python’s Socket Library
Luckily python has an easy to use library. Like other libraries, we import it with thusly:
from socket import *
Many of the socket methods you’ll use are pretty self explanatory:
socket.listen() – listens for incoming connections
socket.accept() – accepts an incoming connection
socket.recv() – returns incoming data as a string
socket.send() – sends data to client socket*
socket.close() – closes the socket
*in this context the ‘client socket’ can be on either the server or client side. When a client connects to a server, the server creates a new client socket on its end. The two clients, one on each end, communicate with each other while the server socket remains open for incoming connections. This becomes more clear as you work with socket connections.
Writing the server
First thing’s first, we need to establish our server socket:
##server.py from socket import * #import the socket library ##let's set up some constants HOST = '' #we are the host PORT = 29876 #arbitrary port not currently in use ADDR = (HOST,PORT) #we need a tuple for the address BUFSIZE = 4096 #reasonably sized buffer for data ## now we create a new socket object (serv) ## see the python docs for more information on the socket types/flags serv = socket( AF_INET,SOCK_STREAM) ##bind our socket to the address serv.bind((ADDR)) #the double parens are to create a tuple with one element serv.listen(5) #5 is the maximum number of queued connections we'll allow
So now we have a server that’s listening for a connection. Or at least we did until the script reached the end and terminated, but we’ll get to that in a bit. Let’s leave our server hanging and jump to our client software.
Creating the client
Start a new python script for the client. We’ll need many of the same constants from the server, but our host will be ‘localhost’. For now we’ll be running both the server and the client on the same machine.
##client.py from socket import * HOST = 'localhost' PORT = 29876 #our port from before ADDR = (HOST,PORT) BUFSIZE = 4096 cli = socket( AF_INET,SOCK_STREAM) cli.connect((ADDR))
Notice that we’re creating another socket object on this end but instead of binding and listening, we’re using the
connect() method to connect to our server.
So what happens if we run our server and then run our client? Well, not much. While our server starts to listen, it then hits the end of the script. We need it to instead wait until it accepts a connection and then do something with that connection.
socket.accept() does just that, and returns two things: a new client socket and the address bound to the socket on the other end. Once we have that, we can send data!
Continuing on server.py:
serv = socket( AF_INET,SOCK_STREAM) ##bind our socket to the address serv.bind((ADDR)) #the double parens are to create a tuple with one element serv.listen(5) #5 is the maximum number of queued connections we'll allow print 'listening...' conn,addr = serv.accept() #accept the connection print '...connected!' conn.send('TEST') conn.close()
The last step is to jump back over to our client and tell our client to expect to receive data:
cli = socket( AF_INET,SOCK_STREAM) cli.connect((ADDR)) data = cli.recv(BUFSIZE) print data cli.close()
Now when you run your server it will wait until a client connects. Once you run your client it will connect and receive a short message (the word “TEST” in this case) and print it to the screen. If you wanted to you could have the client send a response, using the same
recv() methods (but reversed).
Make sure you
close() your connections when you’re done using them. If you don’t close things nicely they have a nasty habit of staying bound/connected until you forcibly kill the python process. This can be a real pain when you’re debugging.
By itself this isn’t particularly useful, especially considering we can only handle one connection at a time and exit once it’s closed. By adding a few while loops and some threading we can make this into something much more valuable. As it is, I’m pretty wiped from the hackathon, so the threading tutorial will have to wait until another day.