import json import signal import socket import struct import threading from datetime import datetime signal.signal(signal.SIGINT, signal.SIG_DFL) PORT = 1234 HEADER_LENGTH = 2 def receive_fixed_length_msg(sock, msglen): message = b'' while len(message) < msglen: chunk = sock.recv(msglen - len(message)) # preberi nekaj bajtov if chunk == b'': raise RuntimeError("socket connection broken") message = message + chunk # pripni prebrane bajte sporocilu return message def receive_json(sock) -> dict: # preberi glavo sporocila (v prvih 2 bytih je dolzina sporocila) header = receive_fixed_length_msg(sock, HEADER_LENGTH) message_length = struct.unpack("!H", header)[0] # pretvori dolzino sporocila v int message = None if message_length > 0: # ce je vse OK message = receive_fixed_length_msg(sock, message_length) # preberi sporocilo message = message.decode("utf-8") message = json.loads(message) return message def send_json(sock, message): message["time"] = datetime.now().strftime("%c") encoded_message = json.dumps(message).encode("utf-8") # pretvori sporocilo v niz bajtov, uporabi UTF-8 kodno tabelo # ustvari glavo v prvih 2 bytih je dolzina sporocila (HEADER_LENGTH) # metoda pack "!H" : !=network byte order, H=unsigned short header = struct.pack("!H", len(encoded_message)) message = header + encoded_message # najprj posljemo dolzino sporocilo, slee nato sporocilo samo sock.sendall(message) def send_error(sock, message): send_json(sock, { "type": "error", "data": message, }) # funkcija za komunikacijo z odjemalcem (tece v loceni niti za vsakega odjemalca) def client_thread(client_sock, client_addr): global clients global usernames print("[system] connected with " + client_addr[0] + ":" + str(client_addr[1])) print("[system] we now have " + str(len(clients)) + " clients") try: while True: # neskoncna zanka msg_received = receive_json(client_sock) msg_type = str(msg_received["type"]) data = str(msg_received["data"]) time = str(msg_received["time"]) if not msg_received: # ce obstaja sporocilo break match msg_type: case "username": usernames[client_sock] = data case "message": username = usernames[client_sock] if username is None: username = client_addr[0] + ":" + str(client_addr[1]) msg_received["username"] = username print(f'[{time}] [{username}] : {data}') if data.startswith("@"): [username, *data] = data.split() username = str(username).replace("@", "") cl = [c for c, u in usernames.items() if u == username] if len(cl) == 0: send_error(client_sock, "Client with this username does not exist.") continue client = cl[0] msg_received["data"] = "".join(data) send_json(client, msg_received) continue for client in clients: if client == client_sock: continue send_json(client, msg_received) except: # tule bi lahko bolj elegantno reagirali, npr. na posamezne izjeme. Trenutno kar pozremo izjemo pass # prisli smo iz neskoncne zanke with clients_lock: clients.remove(client_sock) if client_sock in usernames: usernames.pop(client_sock) print("[system] we now have " + str(len(clients)) + " clients") client_sock.close() # kreiraj socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(("localhost", PORT)) server_socket.listen(1) # cakaj na nove odjemalce print("[system] listening ...") clients = set() usernames = dict() clients_lock = threading.Lock() while True: try: # pocakaj na novo povezavo - blokirajoc klic client_sock, client_addr = server_socket.accept() with clients_lock: clients.add(client_sock) thread = threading.Thread(target=client_thread, args=(client_sock, client_addr)) thread.daemon = True thread.start() except KeyboardInterrupt: break print("[system] closing server socket ...") server_socket.close()