ws_webrtc_server.gd 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. extends Node
  2. enum Message {
  3. JOIN,
  4. ID,
  5. PEER_CONNECT,
  6. PEER_DISCONNECT,
  7. OFFER,
  8. ANSWER,
  9. CANDIDATE,
  10. SEAL,
  11. }
  12. ## Unresponsive clients time out after this time (in milliseconds).
  13. const TIMEOUT = 1000
  14. ## A sealed room will be closed after this time (in milliseconds).
  15. const SEAL_TIME = 10000
  16. ## All alphanumeric characters.
  17. const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  18. var _alfnum := ALFNUM.to_ascii_buffer()
  19. var rand: RandomNumberGenerator = RandomNumberGenerator.new()
  20. var lobbies: Dictionary = {}
  21. var tcp_server := TCPServer.new()
  22. var peers: Dictionary = {}
  23. class Peer extends RefCounted:
  24. var id := -1
  25. var lobby := ""
  26. var time := Time.get_ticks_msec()
  27. var ws := WebSocketPeer.new()
  28. func _init(peer_id: int, tcp: StreamPeer) -> void:
  29. id = peer_id
  30. ws.accept_stream(tcp)
  31. func is_ws_open() -> bool:
  32. return ws.get_ready_state() == WebSocketPeer.STATE_OPEN
  33. func send(type: int, id: int, data: String = "") -> void:
  34. return ws.send_text(JSON.stringify({
  35. "type": type,
  36. "id": id,
  37. "data": data,
  38. }))
  39. class Lobby extends RefCounted:
  40. var peers := {}
  41. var host := -1
  42. var sealed := false
  43. var time := 0 # Value is in milliseconds.
  44. var mesh := true
  45. func _init(host_id: int, use_mesh: bool) -> void:
  46. host = host_id
  47. mesh = use_mesh
  48. func join(peer: Peer) -> bool:
  49. if sealed: return false
  50. if not peer.is_ws_open(): return false
  51. peer.send(Message.ID, (1 if peer.id == host else peer.id), "true" if mesh else "")
  52. for p: Peer in peers.values():
  53. if not p.is_ws_open():
  54. continue
  55. if not mesh and p.id != host:
  56. # Only host is visible when using client-server
  57. continue
  58. p.send(Message.PEER_CONNECT, peer.id)
  59. peer.send(Message.PEER_CONNECT, (1 if p.id == host else p.id))
  60. peers[peer.id] = peer
  61. return true
  62. func leave(peer: Peer) -> bool:
  63. if not peers.has(peer.id):
  64. return false
  65. peers.erase(peer.id)
  66. var close := false
  67. if peer.id == host:
  68. # The room host disconnected, will disconnect all peers.
  69. close = true
  70. if sealed:
  71. return close
  72. # Notify other peers.
  73. for p: Peer in peers.values():
  74. if not p.is_ws_open():
  75. continue
  76. if close:
  77. # Disconnect peers.
  78. p.ws.close()
  79. else:
  80. # Notify disconnection.
  81. p.send(Message.PEER_DISCONNECT, peer.id)
  82. return close
  83. func seal(peer_id: int) -> bool:
  84. # Only host can seal the room.
  85. if host != peer_id:
  86. return false
  87. sealed = true
  88. for p: Peer in peers.values():
  89. if not p.is_ws_open():
  90. continue
  91. p.send(Message.SEAL, 0)
  92. time = Time.get_ticks_msec()
  93. peers.clear()
  94. return true
  95. func _process(_delta: float) -> void:
  96. poll()
  97. func listen(port: int) -> void:
  98. if OS.has_feature("web"):
  99. OS.alert("Cannot create WebSocket servers in Web exports due to browsers' limitations.")
  100. return
  101. stop()
  102. rand.seed = int(Time.get_unix_time_from_system())
  103. tcp_server.listen(port)
  104. func stop() -> void:
  105. tcp_server.stop()
  106. peers.clear()
  107. func poll() -> void:
  108. if not tcp_server.is_listening():
  109. return
  110. if tcp_server.is_connection_available():
  111. var id := randi() % (1 << 31)
  112. peers[id] = Peer.new(id, tcp_server.take_connection())
  113. # Poll peers.
  114. var to_remove := []
  115. for p: Peer in peers.values():
  116. # Peers timeout.
  117. if p.lobby.is_empty() and Time.get_ticks_msec() - p.time > TIMEOUT:
  118. p.ws.close()
  119. p.ws.poll()
  120. while p.is_ws_open() and p.ws.get_available_packet_count():
  121. if not _parse_msg(p):
  122. print("Parse message failed from peer %d" % p.id)
  123. to_remove.push_back(p.id)
  124. p.ws.close()
  125. break
  126. var state := p.ws.get_ready_state()
  127. if state == WebSocketPeer.STATE_CLOSED:
  128. print("Peer %d disconnected from lobby: '%s'" % [p.id, p.lobby])
  129. # Remove from lobby (and lobby itself if host).
  130. if lobbies.has(p.lobby) and lobbies[p.lobby].leave(p):
  131. print("Deleted lobby %s" % p.lobby)
  132. lobbies.erase(p.lobby)
  133. # Remove from peers
  134. to_remove.push_back(p.id)
  135. # Lobby seal.
  136. for k: String in lobbies:
  137. if not lobbies[k].sealed:
  138. continue
  139. if lobbies[k].time + SEAL_TIME < Time.get_ticks_msec():
  140. # Close lobby.
  141. for p: Peer in lobbies[k].peers:
  142. p.ws.close()
  143. to_remove.push_back(p.id)
  144. # Remove stale peers
  145. for id: int in to_remove:
  146. peers.erase(id)
  147. func _join_lobby(peer: Peer, lobby: String, mesh: bool) -> bool:
  148. if lobby.is_empty():
  149. for _i in 32:
  150. lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length() - 1)])
  151. lobbies[lobby] = Lobby.new(peer.id, mesh)
  152. elif not lobbies.has(lobby):
  153. return false
  154. lobbies[lobby].join(peer)
  155. peer.lobby = lobby
  156. # Notify peer of its lobby
  157. peer.send(Message.JOIN, 0, lobby)
  158. print("Peer %d joined lobby: '%s'" % [peer.id, lobby])
  159. return true
  160. func _parse_msg(peer: Peer) -> bool:
  161. var pkt_str: String = peer.ws.get_packet().get_string_from_utf8()
  162. var parsed: Dictionary = JSON.parse_string(pkt_str)
  163. if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("type") or not parsed.has("id") or \
  164. typeof(parsed.get("data")) != TYPE_STRING:
  165. return false
  166. if not str(parsed.type).is_valid_int() or not str(parsed.id).is_valid_int():
  167. return false
  168. var msg := {
  169. "type": str(parsed.type).to_int(),
  170. "id": str(parsed.id).to_int(),
  171. "data": parsed.data,
  172. }
  173. if msg.type == Message.JOIN:
  174. if peer.lobby: # Peer must not have joined a lobby already!
  175. return false
  176. return _join_lobby(peer, msg.data, msg.id == 0)
  177. if not lobbies.has(peer.lobby): # Lobby not found?
  178. return false
  179. var lobby: Lobby = lobbies[peer.lobby]
  180. if msg.type == Message.SEAL:
  181. # Client is sealing the room.
  182. return lobby.seal(peer.id)
  183. var dest_id: int = msg.id
  184. if dest_id == MultiplayerPeer.TARGET_PEER_SERVER:
  185. dest_id = lobby.host
  186. if not peers.has(dest_id): # Destination ID not connected.
  187. return false
  188. if peers[dest_id].lobby != peer.lobby: # Trying to contact someone not in same lobby.
  189. return false
  190. if msg.type in [Message.OFFER, Message.ANSWER, Message.CANDIDATE]:
  191. var source := MultiplayerPeer.TARGET_PEER_SERVER if peer.id == lobby.host else peer.id
  192. peers[dest_id].send(msg.type, source, msg.data)
  193. return true
  194. return false # Unknown message.