123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- from calendar import c
- from distutils.command.clean import clean
- from pickletools import bytes8
- import socket
- import re
- import uuid
- import struct
- from threading import Thread
- # server's IP address
- SERVER_HOST = "0.0.0.0"
- SERVER_PORT = 5002 # port we want to use
- def send_msg(sock, msg, doPack):
- # For 1 byte (bool) send just that
- # Prefix each message with a 4-byte length (network byte order)
- if doPack and len(msg) > 1:
- msg = struct.pack('<I', len(msg)) + msg
- sock.sendall(msg)
- def recv_msg(sock):
- # Read message length and unpack it into an integer
- raw_msglen = recvall(sock, 4)
- if not raw_msglen:
- return None
- msglen = struct.unpack('<I', raw_msglen)[0]
- # Read the message data
- return recvall(sock, msglen)
- def recvall(sock, n):
- # Helper function to recv n bytes or return None if EOF is hit
- data = bytearray()
- while len(data) < n:
- packet = sock.recv(n - len(data))
- if not packet:
- return None
- data.extend(packet)
- return data
- # initialize list/set of all connected client's sockets
- client_sockets = dict()
- class GameConnection:
- server: socket
- client: socket
- serverInit = False
- clientInit = False
- messageQueueIn = []
- messageQueueOut = []
- def __init__(self) -> None:
- self.server = None
- self.client = None
- pass
- class Session:
- total = 1
- joined = 0
- password = ""
- protected = False
- name: str
- host: socket
- host_uuid: str
- players = []
- connections = []
- 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
- def addConnection(self, conn: socket, isServer: bool) -> GameConnection:
- #find uninitialized server connection
- for gc in self.connections:
- if isServer and not gc.serverInit:
- gc.server = conn
- gc.serverInit = True
- return gc
- if not isServer and not gc.clientInit:
- gc.client = conn
- gc.clientInit = True
- return gc
-
- #no existing connection - create the new one
- gc = GameConnection()
- if isServer:
- gc.server = conn
- gc.serverInit = True
- else:
- gc.client = conn
- gc.clientInit = True
- self.connections.append(gc)
- return gc
- def validPipe(self, conn) -> bool:
- for gc in self.connections:
- if gc.server == conn or gc.client == conn:
- return gc.serverInit and gc.clientInit
- return False
- def getPipe(self, conn) -> socket:
- for gc in self.connections:
- if gc.server == conn:
- return gc.client
- if gc.client == conn:
- return gc.server
-
- # 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(10)
- 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 not sender["session"].started:
- 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):
- msg2 = ""
- counter = 0
- for s in sessions.values():
- if not s.started:
- msg2 += f":{s.name}:{s.joined}:{s.total}:{s.protected}"
- counter += 1
- msg = f":>>SESSIONS:{counter}{msg2}"
- 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 startSession(session: Session):
- session.started = True
- session.host_uuid = str(uuid.uuid4())
- hostMessage = f":>>HOST:{session.host_uuid}:{session.joined - 1}" #one client will be connected locally
- #host message must be before start message
- send(session.host, hostMessage)
- for player in session.players:
- client_sockets[player]['uuid'] = str(uuid.uuid4())
- msg = f":>>START:{client_sockets[player]['uuid']}"
- send(player, msg)
- def dispatch(client: socket, sender: dict, arr: bytes):
-
- if arr == None or len(arr) == 0:
- return
- #if len(sender["prevmessages"]):
- # arr = sender["prevmessages"] + arr
- # sender["prevmessages"] = bytes()
- #check for game mode connection
- msg = str(arr)
- if msg.find("Aiya!") != -1:
- sender["pipe"] = True
- if sender["pipe"]:
- if sender["game"]:
- sender["prevmessages"].append(arr)
- else:
- sender["prevmessages"].append(struct.pack('<I', len(arr)) + arr)
- match = re.search(r"\((\w+)\)", msg)
- _appType = ''
- if match != None:
- _appType = match.group(1)
- sender["apptype"] = _appType
-
- _uuid = arr.decode()
- if not _uuid == '' and not sender["apptype"] == '':
- #search for uuid
- for session in sessions.values():
- if session.started:
- if _uuid.find(session.host_uuid) != -1 and sender["apptype"] == "server":
- gc = session.addConnection(client, True)
- #send_msg(gc.server, gc.messageQueueIn)
- #gc.messageQueueIn = bytes()
- sender["session"] = session
- sender["game"] = True
- #read boolean flag for the endian
- sender["prevmessages"].append(client.recv(1))
- #if not gc.clientInit:
- # gc.messageQueueOut += arr
- return
- if sender["apptype"] == "client":
- for p in session.players:
- if _uuid.find(client_sockets[p]["uuid"]) != -1:
- #client connection
- gc = session.addConnection(client, False)
- #send_msg(gc.client, gc.messageQueueOut)
- #gc.messageQueueOut = bytes()
- sender["session"] = session
- sender["game"] = True
- #read boolean flag for the endian
- sender["prevmessages"].append(client.recv(1))
- #if not gc.serverInit:
- # gc.messageQueueIn += arr
- # return
- break
- #game mode
- if sender["pipe"] and sender["game"] and sender["session"].validPipe(client):
- #send messages from queue
- opposite = sender["session"].getPipe(client)
- for x in client_sockets[opposite]["prevmessages"]:
- client.sendall(x)
- client_sockets[opposite]["prevmessages"].clear()
- try:
- for x in sender["prevmessages"]:
- opposite.sendall(x)
- except Exception as e:
- print(f"[!] Error: {e}")
- sender["prevmessages"].clear()
- return
- if sender["pipe"]:
- return
-
- #lobby mode
- msg = arr.decode()
- _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)
- message = f":>>CREATED:{sender['session'].name}"
- send(client, message)
- #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()
- if tag == "READY" and sender["auth"] and sender["joined"] and sender["session"].name == tag_value:
- if sender["session"].joined > 0 and sender["session"].host == client:
- startSession(sender["session"])
- updateSessions()
- dispatch(client, sender, (_nextTag[1] + _nextTag[2]).encode())
- 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
- if client_sockets[cs]["game"]:
- msg = cs.recv(4096)
- else:
- msg = recv_msg(cs)
- #msg = cs.recv(2048)
- 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": "", "joined": False, "game": False, "pipe": False, "apptype": "", "prevmessages": []}
- # 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()
|