0
votes

MY ENVIRONMENT: windows10 & python 3.6

  1. PURPOSE: I want to build an online chat room which means when clients are connected to servers and send messages to server, the server will broadcast messages to every clients that have conncetions with it.
  2. KEY POINT: So there is a problem that i want the client listen to the server and waiting for input at the same time. (code is posted down below)
  3. MY SOLUTION: When I try to solve this problem by threading of python, the result is that if let's say client A and client B have both connected to the server and if client A send multiple messages and client B can only receive the first message or receive duplicate & messy messages. (imgaes of Error are posted down below)
  4. EXPLANATION OF CODE In case my poor develop skills confused you, i want to make a simple explaination of my code.
    1. Before each message was send, the length of the message will be sent first, So it's easy for the recv side to determine how large the buffer should be allocated.
    2. All those "try" and "except" are used to make the system more robust, in case of unexpected disconnection
    3. On the server side, it accept connection requests and deal with each connection by threads. In each thread, if messages were sent from clients, server will then broadcast the messages to other clients.
    4. On the client side, i create a thread to listen to the messages from server and use "input" to keep waiting for input.
  5. QUESTION my questions are:
    How can i realize that: any client are free to send as many messages as possible and other clients can reveive all the messages while waiting for the input.(maybe change the way of input?.eg. not use command "input"?)

IMAGE

IMAGE EXPLANATION
1. server is running on the left window and client A is running on the right window and client B is running on the bottom window.
2. the operation in details is: client B sent "a b c d e" and client A received "a". client A sent "a b c e d" and client B received "[some info..] a \n [some info..] a".
3. We can learn from the image that: when clients send more than one messages before other clients send any messages, then other client will only receive the first message sent by client A or receive duplicate messages.

Finally, sorry for my poor understanding of socket and threading and my poor explanation. Thank you for helping.

code for clinet:

import socket
import time
import threading


SERVER_HOST = "" # server ip
LOCAL_HOST = "10.124.18.206"
HOST = LOCAL_HOST

HEADER = 64
PORT = 42366
CHECK_CLIENT = "!CHECK"
FORMAT = "utf-8"
DISCONNECT_MESSAGE = "!DISCONNECT"
KEEP_MESSAGE = "KEEP"
TIMEOUT = 50


def gettimestamp():
    return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())

def send(msg):
    message = msg.encode(FORMAT)
    msg_length = len(message)
    send_length = str(msg_length).encode(FORMAT)
    send_length += b' ' * (HEADER-len(send_length))
    client.send(send_length)
    client.send(message)

def recv():
    message = client.recv(HEADER)
    msg_length = len(message)
    message = message.decode(FORMAT)
    data = client.recv(msg_length).decode(FORMAT)
    return data

def listen():
    while True:
        data = recv()
        if data:
            print(f"[MESSAGE {gettimestamp()}] {data}" )
        else:
            break

def start():
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    print(f"[STARTING] Connecting to {HOST}:{PORT}")
    client.connect((HOST,PORT))
    send(CHECK_CLIENT)
    client_num = recv()
    print(f"There are {client_num} people in the chat.")
    print("Let's chat!")
    connected = True
    listen_th = threading.Thread(target=listen,args=())
    listen_th.daemon = True
    listen_th.start()
    while connected:
        time.sleep(0.3)
        data = input("[INPUT] ")

        try:
            send(data)
        except ConnectionAbortedError as e:
            connected = False

        if data == DISCONNECT_MESSAGE:
            connected = False

    print(f"[DISCONNECT {gettimestamp()}] {HOST}:{PORT}")
    client.close()

if __name__ == "__main__":
    start()


code for server

# -*- coding: utf-8 -*-

import socket
import threading
import time


SERVER_HOST = "" # server ip
LOCAL_HOST = "10.124.18.206"
HOST = LOCAL_HOST

PORT = 42366
CHECK_CLIENT = "!CHECK"
DISCONNECT_MESSAGE = "!DISCONNECT"
HEADER = 64
FORMAT = "utf-8"
TIMEOUT = 60

clients = []

def gettimestamp():
    return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())

def send(conn,msg):
    try:
        message = msg.encode(FORMAT)
        msg_length = len(message)
        send_length = str(msg_length).encode(FORMAT)
        send_length += b' ' * (HEADER-len(send_length))
        conn.send(send_length)
        conn.send(message)
    except OSError as e:
        print(f"{e} in line 33")
        clients.remove(conn)

def update_chat(msg,conn):
    global clients
    temp = []
    for client in clients:
        flag = 1
        if client != conn:
            try:
                send(client,msg)
            except BrokenPipeError as e:
                print(f"{e} in line 45")
                flag = 0
                continue
        if flag == 1:
            temp.append(conn)

    clients = temp


def handle_client(addr,conn):
    print(f"[NEW CONNECTION {gettimestamp()}] {addr} connected.")
    print(f"[ACTIVE CONNECTIONS {gettimestamp()}] {threading.activeCount()-1}")
    connected = True
    clients.append(conn)
    while connected:
        try:
            conn.settimeout(TIMEOUT)
            msg_length = conn.recv(HEADER).decode(FORMAT)
            conn.settimeout(None)
            if msg_length:
                msg_length = int(msg_length)
                try:
                    msg = conn.recv(msg_length).decode(FORMAT)
                except ConnectionResetError as e:
                    print(f"{e} in line 67")
                    connected = False
                if msg == DISCONNECT_MESSAGE:
                    connected = False
                    clients.remove(conn)
                elif msg == CHECK_CLIENT:
                    client_num = threading.activeCount()-1
                    send(conn,str(client_num))
                else:
                    update_chat(f"{msg}",conn)

                print(f"[{addr[0]+str(addr[1])} {gettimestamp()}] {msg}")

        except ConnectionResetError as e:
            print(f"{e} in line 81")
            connected = False
        except ConnectionAbortedError as e:
            print(f"{e} in line 84")
            continue
        except socket.timeout as e:
            connected = False


    print(f"[DISCONNECTED {gettimestamp()}] {addr}")

    conn.close()

def start():
    print("[STARTING] Server is starting...")
    server = socket.socket()
    print(f"[BIDING] Binding address {HOST}:{PORT}")
    server.bind((HOST,PORT))
    server.listen()
    print(f"[LISTENING {gettimestamp()}] Server is listening on {HOST}:{PORT}")
    while True:
        conn,addr = server.accept()
        thread = threading.Thread(target=handle_client,args=(addr,conn))
        thread.daemon = True
        thread.start()

if __name__ == "__main__":
    start()


1
Sleeps in networking code are just literally a waste of time. - user207421

1 Answers

1
votes
                msg = conn.recv(msg_length).decode(FORMAT)

This will receive up to msg_length bytes. You need to ensure you receive exactly msg_length bytes. You probably want to write a receive all function that calls recv repeatedly until precisely the required number of bytes are received.