proxyServer.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import socket
  2. from threading import Thread
  3. # server's IP address
  4. SERVER_HOST = "0.0.0.0"
  5. SERVER_PORT = 5002 # port we want to use
  6. # initialize list/set of all connected client's sockets
  7. client_sockets = dict()
  8. class Session:
  9. total = 1
  10. joined = 0
  11. password = ""
  12. protected = False
  13. name: str
  14. host: socket
  15. players = []
  16. started = False
  17. def __init__(self, host: socket, name: str) -> None:
  18. self.name = name
  19. self.host = host
  20. self.players = [host]
  21. self.joined += 1
  22. def isJoined(self, player: socket) -> bool:
  23. return player in self.players
  24. def join(self, player: socket):
  25. if not self.isJoined(player) and self.joined < self.total:
  26. self.players.append(player)
  27. self.joined += 1
  28. def leave(self, player: socket):
  29. if not self.isJoined(player) or player == self.host:
  30. return
  31. self.players.remove(player)
  32. self.joined -= 1
  33. # create a TCP socket
  34. s = socket.socket()
  35. # make the port as reusable port
  36. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  37. # bind the socket to the address we specified
  38. s.bind((SERVER_HOST, SERVER_PORT))
  39. # listen for upcoming connections
  40. s.listen(5)
  41. print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")
  42. # list of active sessions
  43. sessions = dict()
  44. def handleDisconnection(client: socket):
  45. sender = client_sockets[client]
  46. if sender["joined"]:
  47. if sender["session"].host == client:
  48. #destroy the session, sending messages inside the function
  49. deleteSession(sender["session"])
  50. else:
  51. sender["session"].leave(client)
  52. sender["joined"] = False
  53. message = f":>>KICK:{sender['session'].name}:{sender['username']}"
  54. for client_socket in sender["session"].players:
  55. client_socket.send(message.encode())
  56. updateStatus(sender["session"])
  57. updateSessions()
  58. client.close()
  59. sender["valid"] = False
  60. def send(client: socket, message: str):
  61. sender = client_sockets[client]
  62. if "valid" not in sender or sender["valid"]:
  63. client.send(message.encode())
  64. def broadcast(clients: list, message: str):
  65. for c in clients:
  66. send(c, message)
  67. def sendSessions(client: socket):
  68. msg = f":>>SESSIONS:{len(sessions.keys())}"
  69. for s in sessions.values():
  70. msg += f":{s.name}:{s.joined}:{s.total}:{s.protected}"
  71. send(client, msg)
  72. def updateSessions():
  73. for s in client_sockets.keys():
  74. sendSessions(s)
  75. def deleteSession(session: Session):
  76. msg = f":>>KICK:{session.name}"
  77. for player in session.players:
  78. client_sockets[player]["joined"] = False
  79. msg2 = msg + f":{client_sockets[player]['username']}"
  80. send(player, msg2)
  81. sessions.pop(session.name)
  82. def updateStatus(session: Session):
  83. msg = f":>>STATUS:{session.joined}"
  84. for player in session.players:
  85. msg += f":{client_sockets[player]['username']}:{client_sockets[player]['ready']}"
  86. broadcast(session.players, msg)
  87. def dispatch(client: socket, sender: dict, msg: str):
  88. if msg == '':
  89. return
  90. _open = msg.partition('<')
  91. _close = _open[2].partition('>')
  92. if _open[0] != '' or _open[1] == '' or _open[2] == '' or _close[0] == '' or _close[1] == '':
  93. print(f"[!] Incorrect message from {sender['address']}: {msg}")
  94. return
  95. _nextTag = _close[2].partition('<')
  96. tag = _close[0]
  97. tag_value = _nextTag[0]
  98. #greetings to the server
  99. if tag == "GREETINGS":
  100. if sender["auth"]:
  101. print(f"[!] Greetings from authorized user {sender['username']} {sender['address']}")
  102. return
  103. print(f"[*] User {sender['address']} autorized as {tag_value}")
  104. sender["username"] = tag_value
  105. sender["auth"] = True
  106. sender["joined"] = False
  107. sendSessions(client)
  108. #VCMI version received
  109. if tag == "VER" and sender["auth"]:
  110. print(f"[*] User {sender['username']} has version {tag_value}")
  111. #message received
  112. if tag == "MSG" and sender["auth"]:
  113. message = f":>>MSG:{sender['username']}:{tag_value}"
  114. if sender["joined"]:
  115. broadcast(sender["session"].players, message)
  116. else:
  117. broadcast(client_sockets.keys(), message)
  118. #new session
  119. if tag == "NEW" and sender["auth"] and not sender["joined"]:
  120. if tag_value in sessions:
  121. #refuse creating game
  122. message = f":>>ERROR:Cannot create session with name {tag_value}, session with this name already exists"
  123. send(client, message)
  124. return
  125. sessions[tag_value] = Session(client, tag_value)
  126. sender["joined"] = True
  127. sender["ready"] = False
  128. sender["session"] = sessions[tag_value]
  129. #set password for the session
  130. if tag == "PSWD" and sender["auth"] and sender["joined"] and sender["session"].host == client:
  131. sender["session"].password = tag_value
  132. sender["session"].protected = tag_value != ""
  133. #set amount of players to the new session
  134. if tag == "COUNT" and sender["auth"] and sender["joined"] and sender["session"].host == client:
  135. if sender["session"].total != 1:
  136. #refuse changing amount of players
  137. message = f":>>ERROR:Changing amount of players is not possible for existing session"
  138. send(client, message)
  139. return
  140. sender["session"].total = int(tag_value)
  141. #now session is ready to be broadcasted
  142. message = f":>>JOIN:{sender['session'].name}:{sender['username']}"
  143. send(client, message)
  144. updateStatus(sender["session"])
  145. updateSessions()
  146. #join session
  147. if tag == "JOIN" and sender["auth"] and not sender["joined"]:
  148. if tag_value not in sessions:
  149. message = f":>>ERROR:Session with name {tag_value} doesn't exist"
  150. send(client, message)
  151. return
  152. if sessions[tag_value].joined >= sessions[tag_value].total:
  153. message = f":>>ERROR:Session {tag_value} is full"
  154. send(client, message)
  155. return
  156. if sessions[tag_value].started:
  157. message = f":>>ERROR:Session {tag_value} is started"
  158. send(client, message)
  159. return
  160. sender["joined"] = True
  161. sender["ready"] = False
  162. sender["session"] = sessions[tag_value]
  163. if tag == "PSWD" and sender["auth"] and sender["joined"] and sender["session"].host != client:
  164. if not sender["session"].protected or sender["session"].password == tag_value:
  165. sender["session"].join(client)
  166. message = f":>>JOIN:{sender['session'].name}:{sender['username']}"
  167. broadcast(sender["session"].players, message)
  168. updateStatus(sender["session"])
  169. updateSessions()
  170. else:
  171. sender["joined"] = False
  172. message = f":>>ERROR:Incorrect password"
  173. send(client, message)
  174. return
  175. #leaving session
  176. if tag == "LEAVE" and sender["auth"] and sender["joined"] and sender["session"].name == tag_value:
  177. if sender["session"].host == client:
  178. #destroy the session, sending messages inside the function
  179. deleteSession(sender["session"])
  180. else:
  181. message = f":>>KICK:{sender['session'].name}:{sender['username']}"
  182. broadcast(sender["session"].players, message)
  183. sender["session"].leave(client)
  184. sender["joined"] = False
  185. updateStatus(sender["session"])
  186. updateSessions()
  187. dispatch(client, sender, _nextTag[1] + _nextTag[2])
  188. def listen_for_client(cs):
  189. """
  190. This function keep listening for a message from `cs` socket
  191. Whenever a message is received, broadcast it to all other connected clients
  192. """
  193. while True:
  194. try:
  195. # keep listening for a message from `cs` socket
  196. msg = cs.recv(2048).decode()
  197. except Exception as e:
  198. # client no longer connected
  199. print(f"[!] Error: {e}")
  200. handleDisconnection(cs)
  201. return
  202. dispatch(cs, client_sockets[cs], msg)
  203. while True:
  204. # we keep listening for new connections all the time
  205. client_socket, client_address = s.accept()
  206. print(f"[+] {client_address} connected.")
  207. # add the new connected client to connected sockets
  208. client_sockets[client_socket] = {"address": client_address, "auth": False, "username": ""}
  209. # start a new thread that listens for each client's messages
  210. t = Thread(target=listen_for_client, args=(client_socket,))
  211. # make the thread daemon so it ends whenever the main thread ends
  212. t.daemon = True
  213. # start the thread
  214. t.start()
  215. # close client sockets
  216. for cs in client_sockets:
  217. cs.close()
  218. # close server socket
  219. s.close()