Chat simulator in Python not working as expected

364 Views Asked by At

What I am trying to do is basically simulate a client-server-client chat using threads. Here is the code so far:

from socket import *
from threading import Thread
import threading
import time
from random import randint

clients = []
HOST = 'localhost'
PORT = 8000

class Server():
    def __init__(self):        
        self.addr = (HOST,PORT)
        global clients

        self.start()
        for i in range(5): Thread(target=self.clientHandler).start()

        self.s.close()

    def clientHandler(self): 
        conn, addr = self.s.accept() 
        clients.append(addr)

        print addr[1], "is Connected" 
        while 1: 
            time.sleep(5)

            # message=repr(data)
            # message=message[1:-1].split(':')
            message='test'                               #for debug
            conn.sendto(message, clients[1])             #for debug
            # conn.sendto(message[1], clients[int(message[0])])

    def start(self):    
        self.s = socket(AF_INET, SOCK_STREAM)
        self.s.bind((HOST, PORT))
        self.s.listen(5)

        print "Server is running......"


class Client(threading.Thread):
    global clients
    def sendMessage(self):
        if len(clients)>1:
                to=randint(0, len(clients)-1)
                message = str(to)+':hello from '+self.name
                print message
                self.ss.send(message)

    def receiveMessage(self):        
        while 1:
            reply=self.ss.recv(1024)
            if reply != '':
                print self.name+" received ", repr(reply)
                break

    def run(self):    
        self.ss = socket(AF_INET, SOCK_STREAM)
        self.ss.connect((HOST, PORT)) # client-side, connects to a host
        self.name=self.ss.getsockname()[1]

        while True: 
            # time.sleep(randint(1,5))
            # self.sendMessage()
            self.receiveMessage()

server=Server()
client1=Client()
client2=Client()
client3=Client()

client1.start()
client2.start()
client3.start()

The idea is that one client should send a message to another random one (have not considered excluding itself yet) over and over again. To do so, the message has the format dest: message, where dest is a random index for choosing a client from the clients list.

After hours of "debugging", I discovered that the sendto() method sends the message to all the clients, not just the one with the specified address (as the above code does). Am I using it wrong?

Also, when I make the clients send messages, they just receive the message right back. What am I doing wrong?

I know it is messy, and sorry about that. I just started working with Python and I am testing multiple methods to see how they work.

Thank you. - Python Noob

1

There are 1 best solutions below

0
On BEST ANSWER

Your code works since the second argument to sendto is ignored for TCP sockets.

For your idea to work, you must actually read something on the server. But first, let's look at the other obvious problems:

You are creating exactly 5 threads on the server. This is not harmful yet, but may be in the future. Instead, just create a thread when needed, as in

class Server(threading.Thread):
    def __init__(self):
        super(Server, self).__init__()
        self.addr = (HOST,PORT)
        self.start()

    def run(self):
        self.s = socket(AF_INET, SOCK_STREAM)
        self.s.bind((HOST, PORT))
        self.s.listen(5)
        print "Server is running......"

        while True:
            conn, addr = self.s.accept()
            threading.Thread(target=self.clientHandler, args=(conn, addr)).start()

Then, don't store the address, store the socket:

def clientHandler(self, conn, addr):
    clients.append(conn)

Furthermore, in clientHandler, read from the sockets:

def clientHandler(self, conn, addr):
    clients.append(conn)
    while True:
         msg = read_from_socket(conn)
         id_str, _, msg = msg.partition(b':')
         clients[int(id_str.decode('ascii'))].sendall(msg)

read_from_socket must be a function that reads your message format. Since TCP is packet-less (imagine it as one infinite stream, until the remote end closes), you must define a message format. Let's do that now, by waiting for the newline b'\n':

def read_from_socket(conn):
    buf = bytearray(0)
    while True:
        b = conn.recv(1)
        buf.extend(b)
        if b == b'\n':
             return bytes(buf)

Then we simply have to adapt the client to add a \n byte:

class Client(threading.Thread):
    def sendMessage(self):
        if len(clients)>1:
                to = randint(0, len(clients) - 1)
                message = ('%s:hello from %s\n' % (to, self.name)).encode('utf-8')
                print(message)
                self.ss.send(message)

Finally, make sure to actually send a message (at the moment, you don't):

server=Server()
client1=Client()
client2=Client()
client3=Client()

client1.start()
client2.start()
client3.start()

time.sleep(1)
client1.sendMessage()

Here is the entire code.