Several problems here, I would advise reading through the answers here as well. I'll show you how to make your solution work below. But this isn't the ideal solution.
Using asyncore
or something else as suggested in the answer above is probably a much better solution. There are things that I haven't handled below - like gracefully killing the server threads, filling up a socket buffer on write, etc... It also still has a limit of handling 5 connections at a time.
Server
Firstly, you should handle the closure of the socket. On close the read call will then return an empty string. If you disconnect a client from your server it will loop infinitely on the recv()
call.
The socket object you receive as conn
from s.accept()
is a socket that allows you to talk on the connection you've just accepted from the client. If you want to send the messages to the other clients, you need to call send
on their connections.
To achieve this you can store and remove the connection objects from a shared list. You can then iterate through these connections in send_data
to send the received message to other connections. You can also avoid sending the message back to the sender.
A server updated to do this is below, you can test this with nc / netcat
nc localhost 4444
instead of using your client python program, because that too has an issue.
#!/usr/bin/python
import socket
from threading import Thread, Lock
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 4444))
# CHANGED:
# threading.Lock() to lock access to the connections list
lock = Lock()
# hold open connections in a list
connections = []
def get_data(s, conn):
data = conn.recv(1024)
print conn, data, "\n"
return data;
def send_data(sender, data):
with lock:
count = 0
for conn in connections:
if conn == sender:
# don't echo back to the originator
continue
try:
print(data)
conn.send(data)
count += 1
except socket.error:
# TODO: add better error handling
pass
# CHANGED: show num clients sent to
print "Data sent to %d clients \n" % count
def listen():
while True:
conn, addr = s.accept()
# CHANGED: add connection to connections list
with lock:
connections.append(conn)
print addr, " connected \n"
while True:
data = get_data(s, conn)
# CHANGED: handle close
if not data:
break
send_data(conn, data)
# CHANGED: remove connection from connections list and close it
with lock:
connections.remove(conn)
conn.close()
s.listen(5)
for i in range(5):
Thread(target = listen).start()
Client
If the server sends data asynchronously to your client sockets, your clients won't read the data because they'll be blocking at the raw_input()
call waiting for user input.
Update: Based on comments below, using select.select()
to wait on stdin will not work on Windows as the select()
system call on Win only supports sockets.
from the docs for select.select()
:
Note File objects on Windows are not acceptable, but sockets are. On Windows, the underlying select() function is provided by the WinSock library, and does not handle file descriptors that don’t originate from WinSock.
Thus another solution that involves threads is probably more appropriate for Windows.
You could solve this by having the socket talk on its own thread and communicate with the main thread via Queues or something. I've opted to use select.select()
to do this within a single thread.
select.select()
allows you to wait on multiple file-like objects for I/O. It accepts three lists, and returns three lists. The first list argument contains file-like objects you are interested in reading from. The first returned list will contained a subset of these that have data to read. The second list argument/return list behaves similarly for writable files and the third is for error conditions. The fourth argument to select.select()
is a timeout, if this timeout is reached and there are no files ready for read/write/error then the call will return with three empty lists.
I've used this to handle user input from stdin and data arriving on the socket.
The insertion of the '>' is slightly broken in this answer though.
import socket
import time
import sys
import select
HOST = '127.0.0.1'
PORT = 4444
NICKNAME = 'NICKNAME_HERE' + ' >'
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
sys.stdout.write(">")
sys.stdout.flush()
readable, writable, errors = select.select([sys.stdin, s], [], [])
if sys.stdin in readable:
message = sys.stdin.readline()
nickandmessage = NICKNAME + message
s.send(nickandmessage)
if s in readable:
data = s.recv(1024)
# TODO: check if socket was closed and exit (data == "")
print(data)
# CHANGED: this was to remove echos? the server fixes that
# if NICKNAME not in data:
# print data
connect()