Browse Source

Add server prototype

nordsoft 3 years ago
parent
commit
2761c59cf0
1 changed files with 274 additions and 0 deletions
  1. 274 0
      launcher/proxyServer.py

+ 274 - 0
launcher/proxyServer.py

@@ -0,0 +1,274 @@
+import socket
+from threading import Thread
+
+# server's IP address
+SERVER_HOST = "0.0.0.0"
+SERVER_PORT = 5002 # port we want to use
+
+# initialize list/set of all connected client's sockets
+client_sockets = dict()
+
+class Session:
+    total = 1
+    joined = 0
+    password = ""
+    protected = False
+    name: str
+    host: socket
+    players = []
+    started = False
+
+    def __init__(self, host: socket, name: str) -> None:
+        self.name = name
+        self.host = host
+        self.players = [host]
+        self.joined += 1
+
+    def isJoined(self, player: socket) -> bool:
+        return player in self.players
+
+    def join(self, player: socket):
+        if not self.isJoined(player) and self.joined < self.total:
+            self.players.append(player)
+            self.joined += 1
+
+    def leave(self, player: socket):
+        if not self.isJoined(player) or player == self.host:
+            return
+
+        self.players.remove(player)
+        self.joined -= 1
+        
+        
+# create a TCP socket
+s = socket.socket()
+# make the port as reusable port
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+# bind the socket to the address we specified
+s.bind((SERVER_HOST, SERVER_PORT))
+# listen for upcoming connections
+s.listen(5)
+print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")
+
+# list of active sessions
+sessions = dict()
+
+
+def handleDisconnection(client: socket):
+    sender = client_sockets[client]
+    if sender["joined"]:
+        if sender["session"].host == client:
+            #destroy the session, sending messages inside the function
+            deleteSession(sender["session"])
+        else:
+            sender["session"].leave(client)
+            sender["joined"] = False
+            message = f":>>KICK:{sender['session'].name}:{sender['username']}"
+            for client_socket in sender["session"].players:
+                client_socket.send(message.encode())
+        updateStatus(sender["session"])
+        updateSessions()
+
+    client.close()
+    sender["valid"] = False
+
+
+def send(client: socket, message: str):
+    sender = client_sockets[client]
+    if "valid" not in sender or sender["valid"]:
+        client.send(message.encode())
+
+
+def broadcast(clients: list, message: str):
+    for c in clients:
+        send(c, message)
+
+
+def sendSessions(client: socket):
+    msg = f":>>SESSIONS:{len(sessions.keys())}"
+    for s in sessions.values():
+        msg += f":{s.name}:{s.joined}:{s.total}:{s.protected}"
+
+    send(client, msg)
+
+
+def updateSessions():
+    for s in client_sockets.keys():
+        sendSessions(s)
+
+
+def deleteSession(session: Session):
+    msg = f":>>KICK:{session.name}"
+    for player in session.players:
+        client_sockets[player]["joined"] = False
+        msg2 = msg + f":{client_sockets[player]['username']}"
+        send(player, msg2)
+    
+    sessions.pop(session.name)
+
+
+def updateStatus(session: Session):
+    msg = f":>>STATUS:{session.joined}"
+    for player in session.players:
+        msg += f":{client_sockets[player]['username']}:{client_sockets[player]['ready']}"
+    broadcast(session.players, msg)
+
+
+def dispatch(client: socket, sender: dict, msg: str):
+    if msg == '':
+        return
+
+    _open = msg.partition('<')
+    _close = _open[2].partition('>')
+    if _open[0] != '' or _open[1] == '' or _open[2] == '' or _close[0] == '' or _close[1] == '':
+        print(f"[!] Incorrect message from {sender['address']}: {msg}")
+        return
+
+    _nextTag = _close[2].partition('<')
+    tag = _close[0]
+    tag_value = _nextTag[0]
+
+    #greetings to the server
+    if tag == "GREETINGS":
+        if sender["auth"]:
+            print(f"[!] Greetings from authorized user {sender['username']} {sender['address']}")
+            return
+        print(f"[*] User {sender['address']} autorized as {tag_value}")
+        sender["username"] = tag_value
+        sender["auth"] = True
+        sender["joined"] = False
+        sendSessions(client)
+
+    #VCMI version received
+    if tag == "VER" and sender["auth"]:
+        print(f"[*] User {sender['username']} has version {tag_value}")
+
+    #message received
+    if tag == "MSG" and sender["auth"]:
+        message = f":>>MSG:{sender['username']}:{tag_value}"
+        if sender["joined"]:
+            broadcast(sender["session"].players, message)
+        else:
+            broadcast(client_sockets.keys(), message)
+
+    #new session
+    if tag == "NEW" and sender["auth"] and not sender["joined"]:
+        if tag_value in sessions:
+            #refuse creating game
+            message = f":>>ERROR:Cannot create session with name {tag_value}, session with this name already exists"
+            send(client, message)
+            return
+        
+        sessions[tag_value] = Session(client, tag_value)
+        sender["joined"] = True
+        sender["ready"] = False
+        sender["session"] = sessions[tag_value]
+
+    #set password for the session
+    if tag == "PSWD" and sender["auth"] and sender["joined"] and sender["session"].host == client:
+        sender["session"].password = tag_value
+        sender["session"].protected = tag_value != ""
+
+    #set amount of players to the new session
+    if tag == "COUNT" and sender["auth"] and sender["joined"] and sender["session"].host == client:
+        if sender["session"].total != 1:
+            #refuse changing amount of players
+            message = f":>>ERROR:Changing amount of players is not possible for existing session"
+            send(client, message)
+            return
+
+        sender["session"].total = int(tag_value)
+        #now session is ready to be broadcasted
+        message = f":>>JOIN:{sender['session'].name}:{sender['username']}"
+        send(client, message)
+        updateStatus(sender["session"])
+        updateSessions()
+
+    #join session
+    if tag == "JOIN" and sender["auth"] and not sender["joined"]:
+        if tag_value not in sessions:
+            message = f":>>ERROR:Session with name {tag_value} doesn't exist"
+            send(client, message)
+            return
+        
+        if sessions[tag_value].joined >= sessions[tag_value].total:
+            message = f":>>ERROR:Session {tag_value} is full"
+            send(client, message)
+            return
+
+        if sessions[tag_value].started:
+            message = f":>>ERROR:Session {tag_value} is started"
+            send(client, message)
+            return
+
+        sender["joined"] = True
+        sender["ready"] = False
+        sender["session"] = sessions[tag_value]
+
+    if tag == "PSWD" and sender["auth"] and sender["joined"] and sender["session"].host != client:
+        if not sender["session"].protected or sender["session"].password == tag_value:
+            sender["session"].join(client)
+            message = f":>>JOIN:{sender['session'].name}:{sender['username']}"
+            broadcast(sender["session"].players, message)
+            updateStatus(sender["session"])
+            updateSessions()
+
+        else:
+            sender["joined"] = False
+            message = f":>>ERROR:Incorrect password"
+            send(client, message)
+            return
+
+    #leaving session
+    if tag == "LEAVE" and sender["auth"] and sender["joined"] and sender["session"].name == tag_value:
+        if sender["session"].host == client:
+            #destroy the session, sending messages inside the function
+            deleteSession(sender["session"])
+        else:
+            message = f":>>KICK:{sender['session'].name}:{sender['username']}"
+            broadcast(sender["session"].players, message)
+            sender["session"].leave(client)
+            sender["joined"] = False
+            updateStatus(sender["session"])
+        updateSessions()
+
+
+    dispatch(client, sender, _nextTag[1] + _nextTag[2])
+
+
+def listen_for_client(cs):
+    """
+    This function keep listening for a message from `cs` socket
+    Whenever a message is received, broadcast it to all other connected clients
+    """
+    while True:
+        try:
+            # keep listening for a message from `cs` socket
+            msg = cs.recv(2048).decode()
+        except Exception as e:
+            # client no longer connected
+            print(f"[!] Error: {e}")
+            handleDisconnection(cs)            
+            return
+            
+        dispatch(cs, client_sockets[cs], msg)
+
+
+while True:
+    # we keep listening for new connections all the time
+    client_socket, client_address = s.accept()
+    print(f"[+] {client_address} connected.")
+    # add the new connected client to connected sockets
+    client_sockets[client_socket] = {"address": client_address, "auth": False, "username": ""}
+    # start a new thread that listens for each client's messages
+    t = Thread(target=listen_for_client, args=(client_socket,))
+    # make the thread daemon so it ends whenever the main thread ends
+    t.daemon = True
+    # start the thread
+    t.start()
+
+ # close client sockets
+for cs in client_sockets:
+    cs.close()
+# close server socket
+s.close()