Simple Python Chat Client & Server

15 March 2012

Provided below is the source code for a relatively simply Python chat client and server program solution which serves as a functional example of socket programming. I apologize for the squished formatting and lack of white space in the source viewer, but viewing the source directly from the text box button should make it a little more readable.

The first file is the server which should run fine on any modern Linux operating system. The client should also be run on Linux, and can connect to the server from any networked computer on the same subnet. I have had some success connecting this client/server to a similar program written in Java, though you may need to play around with the buffer sizes in each.

I also included a few custom commands for the client to execute. The comments in the source should be sufficient to explain what is going on to experienced Python programmers. I know I am terrible for this, but I cannot remember from where I took the original source code before modifying it. Enjoy!

Client.py

import socket
import sys
import time
# Default connection parameters
client_host = "localhost" #Address of server
client_port = int(9020) #Port used by server
time = time.strftime('%l:%M %p %Z on %b %d, %Y') # Client start time
login_time = str(time) #Convert client start time to string
print "Client initiated at: " + login_time
# Override default parameters (optional command line arguments)
if(len(sys.argv) > 1): #One parameter argument provided by client
 client_host = sys.argv[1] #Override host server IP default "localhost" w/ first argument
if(len(sys.argv) > 2): #Two parameter arguments provided by client
 client_port = int(sys.argv[2]) #Override host port number w/ second argument
# Set up client socket connection
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((client_host, client_port))
# Loop indefinitely while client running
while 1:
 server_data = client_socket.recv(2048) #Receive server data into buffer
 client_data = server_data.lower() #Lowercase received data for processing

 # Process received data
 if (client_data == 'adios'):
 client_socket.close() #Close socket connection
 break;
 else:
 print "(SERVER REPLY)",server_data #Feedback from server
 server_data = raw_input("SEND TO SERVER>> ") #Get client input from keyboard
 client_data = server_data.lower() #Lowercase client input

 # Sent commands
 if (client_data != 'adios'):
 client_socket.send(server_data + "\r\n") #Send data to server if not "adios"
 else:
 client_socket.send(server_data + "\r\n")
 client_socket.close #Otherwise close the socket connection
 break;

Server.py

import os
import re
import socket
import sys
import time
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create socket
server_socket.bind(("", 9020)) #Bind server to this socket
server_socket.listen(4) #Max number of queued connections
# Welcome message
print "TCP chat server now awaiting client connection on port 9020..."
chat_log = [] #Contains chat log
time = time.strftime('%l:%M %p %Z on %b %d, %Y') #Server start time formatted nicely
start_time = str(time) #Convert server start time to string
username = "ChatUser" #Default server username if user does not provide one
# Support ~2^x client connections, where x is the number of process forks
os.fork()
os.fork()
os.fork()
# This variable contains the help documentation for the "help" command
chatHelp = ("The chat server accepts the following commands:\n"
+ "adios Closes the program\n"
+ "connection Shows client connection info (IP, port)\n"
+ "get Returns complete chat log\n"
+ "getrange <#> <#> Get chat log entries from <#> to <#> (starts at 1)\n"
+ "help Lists valid commands\n"
+ "name: <text> Sets your username to <text>\n"
+ "test: <text> Echo data back to you <text>\n"
+ "time Shows time when server was initiated\n"
+ "push: <text> Add <text> to chat log\n"
+ "save Save chat log to file\n")
while 1:
 # Accept connection
 client_socket, address = server_socket.accept()

 # Print connection info from client for server log
 print "Received connection from client at", address
# Used in the connection command function (client request) below
 connection = str(address)
# Send welcome string to client
 client_socket.send("Welcome to Nigel's chat room! You are logged in as ChatUser.\n Type help for a list of valid commands.\n")
# Loop indefinitely while server running
 while 1:
 data = client_socket.recv(2048) #Receive client data into buffer
 process_data = data.lower() #Lowercase received data for processing
 print "Data received from client>>", process_data #Print data received from client for log reference

 # Functions for the received commands (I use the find library to reduce compatibility errors with other languages)
 # ---"adios" command function---
 if (process_data.find("adios") == 0):
 client_socket.close() #Close socket connection
 print "<Ctrl+C to exit.>>"
 break;

 # ---"connection:" command function---
 elif(process_data.find("connection") == 0):
 client_socket.send("Client connection info: " + connection + "\n")
 print "User requested connection information"

 # ---"getrange" command function w/ regular expression filtering (must be BEFORE "get" command function)---
 elif(re.match(r'getrange\s+(\d+)\s+(\d+)',process_data)): # Regex to find correct match with dynamic numbers input
 match = re.match(r'getrange\s+(\d+)\s+(\d+)',process_data)
 getValue = "Chat log from range "+ match.group(1) + " and " + match.group(2) + ":\n" # Grab first and second range number provided by client
 if(len(chat_log) >= int(match.group(1)) and len(chat_log) >= int(match.group(2))): # Check to see if chat log extends to given range
 count = int(match.group(1)) - 1
 while(count < int(match.group(2))):
 getValue += chat_log[count] + "\n"
 count += 1
 else:
 getValue += "<>\n" #No data in range provided by client
 client_socket.send(getValue) #Send results to client
# ---"get" command function---
 elif(process_data.find("get") == 0):
 log = "Chat log: \n"
 for item in chat_log:
 log += item+" \n"
 client_socket.send(log)

 # ---"help:" command function---
 elif(process_data.find("help") == 0):
 client_socket.send(chatHelp + "\n")
 print "User requested help"

 # ---"name:" command function---
 elif(process_data.find("name:") == 0):
 username = data[5:].strip() #Only grab the value client set (not "name:")
 client_socket.send("Username set to: " + data[5:] + "\n")

 # ---"test:" command function---
 elif(process_data.find("test:") == 0):
 client_socket.send(data[5:]+"\n") #Echo last 5 elements to client
 print data

 # ---"time" command function---
 elif(process_data.find("time") == 0):
 client_socket.send("Chat server was started at: " + start_time + "\n")
 print "User requested server start time"

 # ---"save" command function---
 elif(process_data.find("save") == 0):
 print "(Saving chat log to file)"
 client_socket.send("Saving chat log to file..." + "\n")
 filename = "chat.log"
 file = open(filename,"w") #Create file
 for item in chat_log: #Loop through elements in chat_log
 file.write("%s\n" % item) #Write elements one by one on a new line
 file.close() #Close/write file

 # ---"push" command function---
 elif(process_data.find("push:") == 0):
 print "(Pushing data to chat log)"
 if(username != ""):
 chat_log.append(username + ": " + data[5:].strip()) #Save actual chat text to log (not "push:")
 else:
 chat_log.append(data[5:].strip())
 client_socket.send("OK\n")
 else:
 print "<<Unknown Data Received>>",data #Server log
 try:
 client_socket.send("Unrecognized command: " + data + "") #Advise client of invalid command
 except socket.error, e:
 print "<<Ctrl+C to exit>>" #Server log
 break;


File System Wars

7 December 2011

For a class presentation I edited a fun little video depicting a humorous battle between Unix File System (UFS), hierarchical file system (HFS), and extensible file system (ext). UFS is depicted by Darth Vader, HFS by Emperor Palpatine/Steve Jobs, and ext by Luke Skywalker.

Unless you’re a computer person familiar with file systems it might not make a whole lot of sense or seem funny at all, but at the very least Emperor Steve is pretty hilarious.

Click on the “Vimeo” button on the bottom right of the player to view a larger version of the video. Otherwise click play and enjoy!


BYU Mobile Student

12 October 2011

BYU Mobile Student

On October 11th my mobile application entitled BYU Mobile Student (v1.0) was certified and released to the Microsoft Zune Marketplace for use with Windows Phone 7, free of charge. You can view the latest version in the marketplace here. Although I imagine that the market for university students at BYU Provo who use a Windows Phone is limited, this app was nonetheless a rewarding exercise in Visual Studio development using C# and Silverlight. Currently the app boasts the following features:

  • Academic Calendar
  • Campus Map
  • Location Map (new w/ version 1.1 release)
  • Testing Center Line Conditions
  • Twitter
  • Useful Links
Now available in the newer version 1.1 release is a GPS location map feature which, when activated, displays the phone’s current location, in addition to labels on roads and on each building of the BYU Provo campus. An issue I have not been able to fix, however, is the fact that Twitter data sometimes has issues loading when the phone is running on the BYU Guest/Secure Wi-Fi networks.

Although I think the WP7 interface is pretty smooth and that the Microsoft App Hub provides some useful tools/support to developers, some of the features in this app were much more difficult to implement than they should have been.

For example, I needed the high-resolution campus map image to be pannable/zoomable within a scrolling control, but due to the lack of availability of built-in elements to handle this (and hours wasted searching for a real solution), I ended up sticking it in a custom web viewer container which luckily had those controls built-in. The downside is that the image has to be downloaded from a remote source instead of local phone storage, causing a slight slowdown during the initial app load time and unwanted dependency on the data connection. With a newer build of the current version 1.1, I was able – with the help of a knowledgeable friend – to finally get the web container to display the image as local instead of remote content.

Another hiccup is that for Microsoft certification, every app candidate must explicitly handle “tombstoning” or app resumption. The known method for saving the current page state before pausing the app and eventually restoring it, however, is buggy in the original version of the Windows Phone 7 OS and crashes the app. I had to work around this by simply not saving the page state but implementing the default behavior anyway in a roundabout way just to satisfy a requirement. With version 1.1 running Mango though the issue has finally been fixed and overall phone performance is much better. Still, a little refinement to the developer tools will take WP7 a long way toward competitiveness against Apple’s iOS.

The WP7 SDK in conjunction with Silverlight is certainly a viable app framework that provides a wealth of functionality with relatively little overhead. Overall I highly recommend it to potential and current Visual Studio developers.


Follow

Get every new post delivered to your Inbox.