0

I'm writing a simple console chat with server and client. When receiving a message from the first client server should send it to the second client and vice versa. But when first client sends a message to the server it returns back and doesn't reach the second client. Maybe there is a problem in receiving() function.

Here is my client.py:

import socket
from _thread import *


def recieving(clientSocket):
    while True:
        encodedMsg = clientSocket.recv(1024)
        decodedMsg = encodedMsg.decode('utf-8')

        print(decodedMsg)


def chat(clientSocket, name):
    msg = input()
    encoded_msg = f'[{name}] {msg}'.encode('utf-8')

    clientSocket.send(encoded_msg)


def main():
    serverAddress = (socket.gethostname(), 4444)

    clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    clientSocket.connect(serverAddress)

    name = input('Enter your name: ')
    start_new_thread(recieving, (clientSocket,))
    while True:
        chat(clientSocket, name)
    

if __name__ == "__main__":
    main()

And server.py:

import time
import socket
from _thread import *


def listen(clientSocket, addr):
    while True:
        encodedMsg = clientSocket.recv(1024)
        decodedMsg = encodedMsg.decode('utf-8')
        currTime = time.strftime("%Y-%m-%d-%H.%M.%S", time.localtime())
    
        for client in clients:
            if addr != client:
                clientSocket.sendto(encodedMsg, client)

    print(f'[{currTime}] {decodedMsg}')


def main():
    serverAddress = (socket.gethostname(), 4444)
    global clients
    clients = []

    serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serverSocket.bind(serverAddress)
    serverSocket.listen(2)

    while True:
        clientSocket, addr = serverSocket.accept()

        if addr not in clients:
            clients.append(addr)
            print(f'{addr} joined chat')

        start_new_thread(listen, (clientSocket, addr))


if __name__ == '__main__':
    main()
lian
  • 399
  • 1
  • 3
  • 16
  • `socket.gethostname()` didn't work on my mac, probably because the hostname isn't in /etc/hosts. This implementation would be equivalent if you used localhost `127.0.0.1` address and would remove the dependency on name resolution – erik258 Sep 10 '22 at 14:29
  • `socket.gethostname()` works fine on my Mac – lian Sep 10 '22 at 14:41
  • because you can resolve your hostname to an address. Check your `/etc/hosts`. There's nothing wrong with using hostname to resolve, it just adds a pointless dns lookup when you know the address you wactually want is 127.0.0.1. – erik258 Sep 10 '22 at 14:42
  • I think this is your bug: ` clientSocket.sendto(encodedMsg, client)` you actually want to send to `client`, but `clientSocket` is already connected to the client it's listening from, so you end up sending messages only to that client. If you try with 3 clients, you'll see the sending client gets the message once for each `sendto` on the sending client's socket. [the doc](https://docs.python.org/3/library/socket.html#socket.socket.sendto) says "The socket should not be connected to a remote socket, since the destination socket is specified by address." – erik258 Sep 10 '22 at 14:44

1 Answers1

1

sendto doesn't work as expected if its socket is connected. It just sends to the connected socket, not the specified address.

Therefore, listen needs to be able to access the open socket of each client in order to write to it.

Currently clients is a list of addresses, but you could change it to a dict of address to socket mappings:

def main():
    global clients
    clients = {}

Then when you get a new client connection, save address and socket:

        clientSocket, addr = serverSocket.accept()

        if addr not in clients:
            clients[addr] = clientSocket
            print(f'{addr} joined chat')

        start_new_thread(listen, (clientSocket, addr))

Finally, in listen, write to each other client's socket, not the connected clientSocket for that listen thread:

        for client in clients:
            if addr != client:
                print(f"sending message from {addr} to {client}")
                clients[client].send(encodedMsg)

There's a number of other problems with your code.

Sockets are not thread safe. So there is a race condition if 2 clients happen to write the same thing at the same time; the writes could be interpolated and the messages munged up.

If a client disconnects, the server doesn't handle the disconnection well. If the server disconnects, the clients go into an infinite loop as well.

erik258
  • 14,701
  • 2
  • 25
  • 31