Skoči na vsebino

RK - 2021/22 - naloga 7 - resitev Starc Aljazs

Nalogo sem naredil "from scrath" z malo pomoci iz ze podanega serverja ter clienta.

Client
import socket as Socket
import os as Os
import threading as Threading
import datetime as Datetime
import json as Json
import time as Time
import sys as Sys
import re as Re
from typing import Dict



SOCKET_PORT = int(Os.getenv('PORT', 1234))
SOCKET_HOST = Os.getenv('HOST', "127.0.0.1")
TERM_COLS, TERM_ROWS = Os.get_terminal_size()

socket: Socket.socket = None
displayname: str = None



def log (level: str, line: str, prefix: str = ""):
    colors = {
        "info": u"\u001b[34m",
        "success": u"\u001b[32m",
        "warn": u"\u001b[33m",
        "error": u"\u001b[31m",
        "log": u"\u001b[37m"
    }
    print(u"%s%-12s [%s%s\u001b[0m] %s" % (
        prefix,
        Datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        colors[level],
        level.upper().center(7),
        line
    ), flush=True)



def receiver(socket: Socket.socket):
    rfile = socket.makefile()
    while True:
        try:
            raw = rfile.readline()
            if not raw:
                continue

            data: Dict = Json.loads(raw)
            if data.get("action") == "message" or data.get("action") == "dm":
                print("\033[%d;%dH[%12s%s %s" % (TERM_ROWS - 1, 0, data.get("from"), ">" if data.get("action") == "dm" else "]", data.get("data")), flush=True)
                print("[%12s] " % displayname, flush=True)
                print("\033[%d;%dH" % (TERM_ROWS - 1, 16), end='', flush=True)

        except Exception as e:
            print("Error")
            print(e)
            pass



def send(data: Dict):
    bdata = bytes(Json.dumps(data) + "\n", "UTF-8")
    socket.send(bdata)



def command(msg: str):
    cmd, *args = msg[1:].split(" ")
    if cmd == "help":
        print("""
\u001b[33mCommands\u001b[0m:
    \u001b[34m/help\u001b[0m
        Display this help menu

    \u001b[34m/msg\u001b[0m <\u001b[34msession\u001b[0m> <\u001b[34mcontent\u001b[0m>
        Send a direct message to a specific session
        <\u001b[34msession\u001b[0m> is the session identificator. You can get list of them with /list
        <\u001b[34mcontent\u001b[0m> is your message content
""")

    elif cmd == "msg":
        send({ "action": "dm", "to": args[0], "data": " ".join(args[1:]) })
    
    else:
        print("\u001b[31mERROR!\u001b[0m Invalid command. Try \u001b[34m/help\u001b[0m for a list of commands")

def defineDisplayname():
    global displayname
    try:
        while not displayname:
            print(chr(27) + "[2J")
            print("\033[%d;%dH" % (TERM_ROWS / 2, TERM_COLS / 2 - 30), flush=True, end="")
            vpis = input("Displayname ^[a-zA-Z0-9]{3,8}$: ")
            if Re.match("^[a-zA-Z0-9]{3,8}$", vpis):
                displayname = vpis
            print(chr(27) + "[2J")
    except KeyboardInterrupt:
        print()
        Sys.exit()


defineDisplayname()

while True:
    try:
        print("\033[%d;%dH" % (TERM_ROWS - 1, 0), flush=True)
        print("[      \u001b[34msystem\u001b[0m] Use \u001b[34m/help\u001b[0m for a list of commands")
        print("[      \u001b[34msystem\u001b[0m] connecting to chat server ... ", end="", flush=True)
        socket = Socket.socket(Socket.AF_INET, Socket.SOCK_STREAM)
        connected = False
        while not connected:
            try:
                socket.connect((SOCKET_HOST, SOCKET_PORT))
                connected = True
                send({ "action": "setname", "data": displayname })
            except Exception:
                False
            Time.sleep(1.0)
        print("\u001b[32mCONNECTED\u001b[0m!")

        thread = Threading.Thread(target=receiver, args=(socket,))
        thread.daemon = True
        thread.start()

        while True:
            try:
                print("[%12s] " % displayname)
                print("\033[%d;%dH" % (TERM_ROWS - 1, 16), end='', flush=True)
                vpis = input("")
                
                if not vpis:
                    print ("\033[A" + " " * TERM_COLS + "\033[A", flush=True)
                    continue
                
                if vpis.startswith("/"):
                    command(vpis)
                else:
                    send({ "action": "message", "data": vpis })
            except Exception as e:
                log("error", e)
                print("Something went wrong! Restarting...")
                break
    except KeyboardInterrupt:
        print()
        Sys.exit()
Server
import socket as Socket
import os as Os
import threading as Threading
import datetime as Datetime
import json as Json
import time as Time

from typing import Dict, Set, TextIO



SOCKET_PORT = int(Os.getenv('PORT', 1234))
SOCKET_BIND = Os.getenv('BIND', "0.0.0.0")



def log (level: str, line: str, prefix: str = ""):
    colors = {
        "info": u"\u001b[34m",
        "success": u"\u001b[32m",
        "warn": u"\u001b[33m",
        "error": u"\u001b[31m",
        "log": u"\u001b[37m"
    }
    print(u"%s%-12s [%s%s\u001b[0m] %s" % (
        prefix,
        Datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        colors[level],
        level.upper().center(7),
        line
    ), flush=True)



class Client():

    socket: Socket.socket = None
    rfile: TextIO = None
    address = None
    name: str = None
    thread: Threading.Thread = None


    def __init__(self, socket: Socket.socket, address) -> None:
        self.socket = socket
        self.address = address
        self.rfile = socket.makefile()
        self.name = address[0] + ":" + str(address[1])
        self.thread = Threading.Thread(target=self.entrypoint)
        self.thread.daemon = True
        self.thread.start()


    def entrypoint (self):
        """
        The client thread entrypoint
        """
        log("log", "[client] %s connected " % self.name)
        emptyCount = 0
        while True:
            try:
                content = self.rfile.readline()
                if not content:
                    emptyCount += 1
                    if emptyCount > 10: break
                    continue
                emptyCount = 0
                data: Dict = Json.loads(content)
                
                if (data.get('action') == 'setname'):
                    self.name = data.get("data")

                if (data.get('action') == 'message'):
                    for client in clients:
                        if client.name != self.name:
                            client.send({ "action": "message", "from": self.name, "data": data.get('data') })

                if (data.get('action') == 'dm'):
                    print(data.get('to'))
                    print(data.get('data'))
                    for client in clients:
                        if client.name == data.get('to'):
                            client.send({ "action": "dm", "from": self.name, "data": data.get('data') })


            except:
                pass
        self.close()


    def send(self, data: Dict):
        """
        Send data to client
        """
        bdata = bytes(Json.dumps(data) + "\n", "UTF-8")
        self.socket.send(bdata)


    def close(self):
        """
        Gracefully close the client socket
        """
        with server_lock:
            log("log", "[client] %s closing ..." % self.name)
            self.socket.close()
            clients.remove(self)
            log("warn", "[client] %s closed" % self.name)



log("info", "[system] Starting server on port %d ..." % SOCKET_PORT)
clients: Set[Client] = set()
server_lock = Threading.Lock()

server_socket = Socket.socket(Socket.AF_INET, Socket.SOCK_STREAM)
server_socket.bind((SOCKET_BIND, SOCKET_PORT))
server_socket.listen(10)
log("success", "[system] Started server on port %d ..." % SOCKET_PORT)



log("info", "[system] Starting healthchecker")
def healthchecker():
    while True:
        for client in list(clients):
            try:
                client.send({"action": "healthcheck"})
                # client.send({"action": "message", "sender": "server", "content": "healthcheck!"})
            except: pass
        Time.sleep(5)

healthchecker_thread = Threading.Thread(target=healthchecker)
healthchecker_thread.daemon = True
healthchecker_thread.start()
log("success", "[system] Started healthchecker")



while True:
    try:
        socket, address = server_socket.accept()

        with server_lock:
            client = Client(socket, address)
            clients.add(client)

    except KeyboardInterrupt:
        break
    except:
        pass



log("warn", "[system] Closing server ...", "\n")
for client in list(clients):
    client.close()

server_socket.close()
log("info", "[system] Server closed")

Zadnja posodobitev: May 2, 2022