11

This is my server program, how can it send the data received from each client to every other client?

import socket
import os
from threading import Thread
import thread

def listener(client, address):
    print "Accepted connection from: ", address

    while True:
        data = client.recv(1024)
        if not data:
            break
        else:
            print repr(data)
            client.send(data)

    client.close()

host = socket.gethostname()
port = 10016

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host,port))
s.listen(3)
th = []

while True:
    print "Server is listening for connections..."
    client, address = s.accept()
    th.append(Thread(target=listener, args = (client,address)).start())

s.close()
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Don't try to post Python code as a runnable code snippet; that only works for JS/HTML/CSS. (I've edited it this time.) – abarnert Nov 26 '14 at 00:31
  • As a side note, your code has a mix of tabs and spaces. This is a really bad idea; it can easily lead to Python not understanding the block structure the same way you or another human does. You should consider switching to an editor that helps you avoid that problem, and also run the script with `python -t` or `python -tt` to verify that you've fixed it and haven't recreated it. – abarnert Nov 26 '14 at 00:33
  • 1
    Please take a look of this SO accepted answer [Python SocketServer: sending to multiple clients?](http://stackoverflow.com/questions/3670127/python-socketserver-sending-to-multiple-clients), perhaps this is exactly what you're looking for, especially with all the suggested points by @abarnert being addressed within a small implementation sample. – Anzel Nov 26 '14 at 00:43
  • @Anzel: I personally wouldn't recommend `asyncore`, but it's still worth looking at that answer. (And, since comments occasionally get deleted, I copied the info into my answer.) – abarnert Nov 26 '14 at 00:52
  • @abarnert, could you please elaborate a little more why you oppose the use of `asyncore`? To send multiple message to clients I do think it's a good choice, so I want to learn more about this from your perspective – Anzel Nov 26 '14 at 00:59
  • @Anzel: Guido has a long post somewhere about what's wrong with `asyncore`, and I doubt I could say it as well as him. (Note that the [3.x docs](https://docs.python.org/3/library/asyncore.html) say "This module exists for backwards compatibility only".) But the short version is that it adds a lot more scaffolding than needed for simple cases, and isn't composable for complex cases, so there's a pretty narrow range where it's useful. – abarnert Nov 26 '14 at 01:10
  • @abarnert, thanks for the pointer, I will look for the post and read more docs related to this matter. Cheers :) – Anzel Nov 26 '14 at 01:12
  • @Anzel: Actually, to me (but maybe not to Guido…), the best argument is that if you're writing a new server from scratch today, you really should be writing it in 3.4 or later. If you've got thousands of lines of existing 2.7 code, definitely use 2.7. If you're slapping together a quick&dirty script and 2.7 is on the machine, go for it. But for something brand new and substantial, why handicap yourself? – abarnert Nov 26 '14 at 01:25
  • I had a similar problem, solved w. server/client code here: [enter link description here](http://stackoverflow.com/questions/41785969/python-tcp-server-accepting-connections-and-broadcasting-commands/41786133#41786133) – Crosswind Jones Jan 22 '17 at 19:21

1 Answers1

13

If you need to send a message to all clients, you need to keep a collection of all clients in some way. For example:

clients = set()
clients_lock = threading.Lock()

def listener(client, address):
    print "Accepted connection from: ", address
    with clients_lock:
        clients.add(client)
    try:    
        while True:
            data = client.recv(1024)
            if not data:
                break
            else:
                print repr(data)
                with clients_lock:
                    for c in clients:
                        c.sendall(data)
    finally:
        with clients_lock:
            clients.remove(client)
            client.close()

It would probably be clearer to factor parts of this out into separate functions, like a broadcast function that did all the sends.

Anyway, this is the simplest way to do it, but it has problems:

  • If one client has a slow connection, everyone else could bog down writing to it. And while they're blocking on their turn to write, they're not reading anything, so you could overflow the buffers and start disconnecting everyone.
  • If one client has an error, the client whose thread is writing to that client could get the exception, meaning you'll end up disconnecting the wrong user.

So, a better solution is to give each client a queue, and a writer thread servicing that queue, alongside the reader thread. (You can then extend this in all kinds of ways—put limits on the queue so that people stop trying to talk to someone who's too far behind, etc.)


As Anzel points out, there's a different way to design servers besides using a thread (or two) per client: using a reactor that multiplexes all of the clients' events.

Python 3.x has some great libraries for this built in, but 2.7 only has the clunky and out-of-date asyncore/asynchat and the low-level select.

As Anzel says, Python SocketServer: sending to multiple clients has an answer using asyncore, which is worth reading. But I wouldn't actually use that. If you want to write a reactor-based server in Python 2.x, I'd either use a better third-party framework like Twisted, or find or write a very simple one that sits directly on select.

Community
  • 1
  • 1
abarnert
  • 354,177
  • 51
  • 601
  • 671