ws_webrtc_server.gd 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. extends Node
  2. const TIMEOUT = 1000 # Unresponsive clients times out after 1 sec
  3. const SEAL_TIME = 10000 # A sealed room will be closed after this time
  4. const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  5. var _alfnum = ALFNUM.to_ascii()
  6. var rand: RandomNumberGenerator = RandomNumberGenerator.new()
  7. var lobbies: Dictionary = {}
  8. var server: WebSocketServer = WebSocketServer.new()
  9. var peers: Dictionary = {}
  10. class Peer extends Reference:
  11. var id = -1
  12. var lobby = ""
  13. var time = OS.get_ticks_msec()
  14. func _init(peer_id):
  15. id = peer_id
  16. class Lobby extends Reference:
  17. var peers: Array = []
  18. var host: int = -1
  19. var sealed: bool = false
  20. var time = 0
  21. func _init(host_id: int):
  22. host = host_id
  23. func join(peer_id, server) -> bool:
  24. if sealed: return false
  25. if not server.has_peer(peer_id): return false
  26. var new_peer: WebSocketPeer = server.get_peer(peer_id)
  27. new_peer.put_packet(("I: %d\n" % (1 if peer_id == host else peer_id)).to_utf8())
  28. for p in peers:
  29. if not server.has_peer(p):
  30. continue
  31. server.get_peer(p).put_packet(("N: %d\n" % peer_id).to_utf8())
  32. new_peer.put_packet(("N: %d\n" % (1 if p == host else p)).to_utf8())
  33. peers.push_back(peer_id)
  34. return true
  35. func leave(peer_id, server) -> bool:
  36. if not peers.has(peer_id): return false
  37. peers.erase(peer_id)
  38. var close = false
  39. if peer_id == host:
  40. # The room host disconnected, will disconnect all peers.
  41. close = true
  42. if sealed: return close
  43. # Notify other peers.
  44. for p in peers:
  45. if not server.has_peer(p): return close
  46. if close:
  47. # Disconnect peers.
  48. server.disconnect_peer(p)
  49. else:
  50. # Notify disconnection.
  51. server.get_peer(p).put_packet(("D: %d\n" % peer_id).to_utf8())
  52. return close
  53. func seal(peer_id, server) -> bool:
  54. # Only host can seal the room.
  55. if host != peer_id: return false
  56. sealed = true
  57. for p in peers:
  58. server.get_peer(p).put_packet("S: \n".to_utf8())
  59. time = OS.get_ticks_msec()
  60. return true
  61. func _init():
  62. server.connect("data_received", self, "_on_data")
  63. server.connect("client_connected", self, "_peer_connected")
  64. server.connect("client_disconnected", self, "_peer_disconnected")
  65. func _process(delta):
  66. poll()
  67. func listen(port):
  68. stop()
  69. rand.seed = OS.get_unix_time()
  70. server.listen(port)
  71. func stop():
  72. server.stop()
  73. peers.clear()
  74. func poll():
  75. if not server.is_listening():
  76. return
  77. server.poll()
  78. # Peers timeout.
  79. for p in peers.values():
  80. if p.lobby == "" and OS.get_ticks_msec() - p.time > TIMEOUT:
  81. server.disconnect_peer(p.id)
  82. # Lobby seal.
  83. for k in lobbies:
  84. if not lobbies[k].sealed:
  85. continue
  86. if lobbies[k].time + SEAL_TIME < OS.get_ticks_msec():
  87. # Close lobby.
  88. for p in lobbies[k].peers:
  89. server.disconnect_peer(p)
  90. func _peer_connected(id, protocol = ""):
  91. peers[id] = Peer.new(id)
  92. func _peer_disconnected(id, was_clean = false):
  93. var lobby = peers[id].lobby
  94. print("Peer %d disconnected from lobby: '%s'" % [id, lobby])
  95. if lobby and lobbies.has(lobby):
  96. peers[id].lobby = ""
  97. if lobbies[lobby].leave(id, server):
  98. # If true, lobby host has disconnected, so delete it.
  99. print("Deleted lobby %s" % lobby)
  100. lobbies.erase(lobby)
  101. peers.erase(id)
  102. func _join_lobby(peer, lobby) -> bool:
  103. if lobby == "":
  104. for _i in range(0, 32):
  105. lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length()-1)])
  106. lobbies[lobby] = Lobby.new(peer.id)
  107. elif not lobbies.has(lobby):
  108. return false
  109. lobbies[lobby].join(peer.id, server)
  110. peer.lobby = lobby
  111. # Notify peer of its lobby
  112. server.get_peer(peer.id).put_packet(("J: %s\n" % lobby).to_utf8())
  113. print("Peer %d joined lobby: '%s'" % [peer.id, lobby])
  114. return true
  115. func _on_data(id):
  116. if not _parse_msg(id):
  117. print("Parse message failed from peer %d" % id)
  118. server.disconnect_peer(id)
  119. func _parse_msg(id) -> bool:
  120. var pkt_str: String = server.get_peer(id).get_packet().get_string_from_utf8()
  121. var req = pkt_str.split("\n", true, 1)
  122. if req.size() != 2: # Invalid request size
  123. return false
  124. var type = req[0]
  125. if type.length() < 3: # Invalid type size
  126. return false
  127. if type.begins_with("J: "):
  128. if peers[id].lobby: # Peer must not have joined a lobby already!
  129. return false
  130. return _join_lobby(peers[id], type.substr(3, type.length() - 3))
  131. if not peers[id].lobby: # Messages across peers are only allowed in same lobby
  132. return false
  133. if not lobbies.has(peers[id].lobby): # Lobby not found?
  134. return false
  135. var lobby = lobbies[peers[id].lobby]
  136. if type.begins_with("S: "):
  137. # Client is sealing the room
  138. return lobby.seal(id, server)
  139. var dest_str: String = type.substr(3, type.length() - 3)
  140. if not dest_str.is_valid_integer(): # Destination id is not an integer
  141. return false
  142. var dest_id: int = int(dest_str)
  143. if dest_id == NetworkedMultiplayerPeer.TARGET_PEER_SERVER:
  144. dest_id = lobby.host
  145. if not peers.has(dest_id): # Destination ID not connected
  146. return false
  147. if peers[dest_id].lobby != peers[id].lobby: # Trying to contact someone not in same lobby
  148. return false
  149. if id == lobby.host:
  150. id = NetworkedMultiplayerPeer.TARGET_PEER_SERVER
  151. if type.begins_with("O: "):
  152. # Client is making an offer
  153. server.get_peer(dest_id).put_packet(("O: %d\n%s" % [id, req[1]]).to_utf8())
  154. elif type.begins_with("A: "):
  155. # Client is making an answer
  156. server.get_peer(dest_id).put_packet(("A: %d\n%s" % [id, req[1]]).to_utf8())
  157. elif type.begins_with("C: "):
  158. # Client is making an answer
  159. server.get_peer(dest_id).put_packet(("C: %d\n%s" % [id, req[1]]).to_utf8())
  160. return true