Ever wondered how today’s chat apps are so sophisticated? We have all kinds of features like media transfer, multiperson chat, link previews, stickers, gifs, etc. I’m just wondering how it all started, IRCs. So I tried to make a simple multiperson chat room.
It’s quite a basic one, but it sure was satisfying to see it work. I also added nicknames support!
The main idea is pretty standard, you get a connection to the server port, you bind that client to another port to make the main port available for the other clients to connect to. After you get an input from one of the clients, broadcast it to all the other clients except the sender, and just continue doing it forever.
Here’s server.py, also downloadable here.
#!/usr/bin/python
import sys
import socket
import select
import threading
from pprint import pprint # var_dump
if len(sys.argv) != 3:
print "Usage: server.py [server_ip] [server_port]"
print " Sets up a basic chat server"
print " For localhost connections, set server_ip to 127.0.0.1"
exit()
bind_ip = sys.argv[1]
bind_port = int(sys.argv[2])
srv_tuple = (bind_ip, bind_port)
server = socket.socket()
server.bind(srv_tuple)
server.listen(5) # 5 max clients
print "[.] Server started"
print "[.] Now listening for clients on (IP: %s | Port: %d)" % srv_tuple
def handle_client(client_socket, client_addr):
# init / welcome
client_socket.send("Please enter your nickname")
nick = client_socket.recv(1048)
connected_clients_nicks.append(nick)
client_socket.send("Enjoy your stay %s!\n\n" % nick)
bc("[connected]", client_socket)
while True:
client_socket.send("%s> " % nick)
msg = client_socket.recv(1048)
if len(msg):
print "[.] %s> %s" % (nick, msg)
bc(msg, client_socket)
else:
print "[-] %s disconnected" % (nick)
bc("[disconnected]", client_socket)
connected_clients.remove(client_socket)
connected_clients_nicks.remove(nick)
client_socket.close()
return
def bc(msg, sender):
idx = 0
for client in connected_clients:
if client != sender:
try:
client.send("\n%s> %s\n%s>" % (
connected_clients_nicks[connected_clients.index(sender)],
msg,
connected_clients_nicks[connected_clients.index(client)]
))
except Exception as e:
print "broadcast exception %s" % (e)
idx = idx + 1
# server main thread
connected_clients = []
connected_clients_nicks = []
while True:
# wait for incoming connection
client,addr = server.accept()
print "[+] Accepted connection from %s, binded to port %d" % (addr[0], addr[1])
# add to list
connected_clients.append(client)
# create a new thread for this client and start it
client = threading.Thread(target=handle_client,args=(client,addr,))
client.start()
The prompts need a little more work too as I’m still trying to figure out the finicky whitespaces and newlines when being sent over the network because they are supposed to be the way to know if a client has disconnected or just spamming the enter
key. But they work fine for now. In the end, I solved the problem by editing the client.py making sure that nothing is sent if the stdin input is nothing but whitespaces.
Here’s client.py, also downloadable here.
#!/usr/bin/python
import sys
import socket
import select
import string
from pprint import pprint # var_dump
if len(sys.argv) != 3:
print "Usage: client.py [server_ip] [server_port]"
print " Connects to a basic chat server"
print " For localhost connections, set server_ip to 127.0.0.1"
exit()
target_host = sys.argv[1]
target_port = int(sys.argv[2])
sys.stdin.flush()
srv = socket.socket()
srv.connect((target_host,target_port))
# init / welcome
print srv.recv(1048)
mynick = string.rstrip(sys.stdin.readline())
srv.send(mynick)
print srv.recv(1048)
while True:
read = []
read,write,exception = select.select([sys.stdin, srv],[],[])
for stream in read:
if stream == srv:
sys.stdout.write(string.rstrip(srv.recv(1048)))
sys.stdout.write(" ")
sys.stdout.flush()
else:
msg = string.rstrip(sys.stdin.readline())
sys.stdin.flush()
srv.send(msg)
Another tricky part is watching 2 input streams, one from the server and another from stdin, but we found the solution: select
, which is quite hard to understand at first, but essentially we’re looping over the 2 input streams, watching for new data to come in, and doing a set of instructions on the data based on where it’s from. and now we’ve got a fully working server and client with a good disconnect behavior.
This was quite a large amount of trial and error, but we got there in the end!
Next project: Python Simple Chat through Proxy › |