PuffedRiceCrackers
PuffedRiceCrackers

Reputation: 785

Sending messages to all clients

This is probably very simple socket message exchange programming, but with my flimsy idea on socket and all, I could not really make it work properly so I'm reaching out for help.

Scenario : Two clients send messages. First one sends Halo and Seeya. After the first sends those messages, the second sends Hello and Bye (This client will just time sleep 6 secs to keep this order). To all messages, server replies with (original msg), client (number)!and a reply message is broadcasted to both clients.

So ideally, the result on both clients would look like this :

    Halo, client 1!
    Seeya, client 1!

    Hello, client 2!
    Bye, client 2 !

I couldn't make it to numbering each client, but here is my code that works weird.

server

    import socket             

    clients = []

    # send msgs to every client
    def broadcast(message):          
        for client in clients : 
            client.send(message)

    # connection part
    s = socket.socket()         
    s.bind(('127.0.0.1', 7070)) 
    s.listen(2)    

    while True:
        c, addr = s.accept()
        if c:
            clients.append(c)
        msg = c.recv(1024).decode()
        msg += ', client!'
        broadcast(msg.encode())

client1

    ### here goes connection part ###

    s.send(("Halo").encode())
    print(f"server = {(s.recv(1024)).decode()}")

    # I've tried adding socket closing/connection part here

    s.send(("Seeya").encode())
    print((s.recv(1024)).decode())

    time.sleep(3)
    s.close()

client2 - first connected, but waits 6 secs for message order

    ### here goes connection part ###

    time.sleep(6)               # waits for message order
    s.send(("Hello").encode())
    print(f"server = {(s.recv(1024)).decode()}")

    # I've tried adding socket closing/connection part here

    s.send(("Bye").encode())
    print((s.recv(1024)).decode())
    time.sleep(3)
    s.close()

The result I get is...

    # On client 1 side 
    Halo, client!                  # no Seeya, Hello, Bye
                                   # connection isn't closed

    # On client 2 side
    Hello, client!                 # no Seeya, Bye
    Halo, client!                  # connection is closed

Upvotes: 3

Views: 3137

Answers (1)

Gil Hamilton
Gil Hamilton

Reputation: 12347

You have several issues going on here. The first and primary one is that your server's main loop is messed up. Each time through the loop, your server wants to accept a connection. So, the first client to connect gets accepted and immediately its first message is received. But the other client hasn't yet been accepted and so will not receive this first message. Then the second client connection is accepted and its first message is then sent to both clients, but then the loop iterates again and no more messages will be sent from the server until a third client connects. Etc.

So you need to separate accepting connections and receiving messages. This can be done in several ways. The most straight-forward way is to use the select function to wait on a number of sockets at once. That is, if you have a list of sockets including the listening socket and previously accepted ones, you'd do something like this:

# List starts with only listening socket
list_of_sockets = [lsock]
...
while True:
    # Wait until some socket becomes "readable"
    rfds, _wfds, _xfds = select.select(list_of_socks, [], [])
    for sock in rfds:
        if sock is lsock:
            # Listening socket ready. Accept new connection
            c, addr = lsock.accept()
            print(f"Accepted {c}")
            list_of_socks.append(c)
        else:
            msg = sock.recv(1024)
            if msg:
                # Received data from connected socket. Send to all
                print(f"Got {msg.decode()} from {sock}")
                broadcast(msg)
            else:
                # Got end of file (this client closed). Remove client from list
                print(f"Closed {sock}")
                list_of_socks.remove(sock)

Another issue with your code that will not be addressed by the server code above: You cannot assume that each message you send will be received as a distinct unit. That is, if the server sends 'Halo' and then it sends 'Hello' before you (a client) have done a recv, then in all likelihood, all the data will be returned in one fell swoop; that is 'HaloHello'.

Generally therefore you will want to put some kind of separator in the data (like a newline [\n] -- but then you'll need to parse the received data) or, better yet, place a fixed-length field in front of each message, giving the length of the subsequent variable-length part, so that you can receive and process exactly one message at a time. (In python, this typically involves using the struct module's pack and unpack functions.) As a result, your current client code will probably not properly sequence messages as you wish.

Also -- though it is less likely to cause a problem -- the same goes for send: you should not assume that send(N) sends exactly N bytes. It might send 1, 2, 3 or N-1 bytes. You can use sendall to ensure that all bytes are sent.

Upvotes: 2

Related Questions