mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2025-12-16 13:30:07 +01:00
[Net] Update & refactor WebSocket Chat demo.
Uses new unified StreamPeer, dropped the multiplayer part (in favor of the dedicated WebSocket demo), add reference WebSocketClient and WebSocketServer signal-based implementations that can be used as drop-in nodes in any project. Might be worth maintaning it as a separate addon.
This commit is contained in:
85
networking/websocket_chat/chat.tscn
Normal file
85
networking/websocket_chat/chat.tscn
Normal file
@@ -0,0 +1,85 @@
|
||||
[gd_scene format=3 uid="uid://cyvrywci15kev"]
|
||||
|
||||
[node name="Chat" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Listen" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_right = 1152.0
|
||||
|
||||
[node name="Connect" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_top = 4.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 35.0
|
||||
|
||||
[node name="Host" type="LineEdit" parent="Panel/VBoxContainer/Connect"]
|
||||
offset_right = 930.0
|
||||
offset_bottom = 31.0
|
||||
size_flags_horizontal = 3
|
||||
text = "ws://localhost:8000/test/"
|
||||
placeholder_text = "ws://my.server/path/"
|
||||
|
||||
[node name="Connect" type="Button" parent="Panel/VBoxContainer/Connect"]
|
||||
offset_left = 934.0
|
||||
offset_right = 1006.0
|
||||
offset_bottom = 31.0
|
||||
toggle_mode = true
|
||||
text = "Connect"
|
||||
|
||||
[node name="Port" type="SpinBox" parent="Panel/VBoxContainer/Connect"]
|
||||
offset_left = 1010.0
|
||||
offset_right = 1093.0
|
||||
offset_bottom = 31.0
|
||||
min_value = 1.0
|
||||
max_value = 65535.0
|
||||
value = 8000.0
|
||||
|
||||
[node name="Listen" type="Button" parent="Panel/VBoxContainer/Connect"]
|
||||
offset_left = 1097.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 31.0
|
||||
toggle_mode = true
|
||||
text = "Listen"
|
||||
|
||||
[node name="Send" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_top = 39.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 70.0
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Panel/VBoxContainer/Send"]
|
||||
offset_right = 1101.0
|
||||
offset_bottom = 31.0
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Enter some text to send..."
|
||||
|
||||
[node name="Send" type="Button" parent="Panel/VBoxContainer/Send"]
|
||||
offset_left = 1105.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 31.0
|
||||
text = "Send"
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="Panel/VBoxContainer"]
|
||||
offset_top = 74.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 648.0
|
||||
size_flags_vertical = 3
|
||||
47
networking/websocket_chat/client.gd
Normal file
47
networking/websocket_chat/client.gd
Normal file
@@ -0,0 +1,47 @@
|
||||
extends Control
|
||||
|
||||
@onready var _client : WebSocketClient = $WebSocketClient
|
||||
@onready var _log_dest = $Panel/VBoxContainer/RichTextLabel
|
||||
@onready var _line_edit = $Panel/VBoxContainer/Send/LineEdit
|
||||
@onready var _host = $Panel/VBoxContainer/Connect/Host
|
||||
|
||||
func info(msg):
|
||||
print(msg)
|
||||
_log_dest.add_text(str(msg) + "\n")
|
||||
|
||||
|
||||
# Client signals
|
||||
func _on_web_socket_client_connection_closed():
|
||||
var ws = _client.get_socket()
|
||||
info("Client just disconnected with code: %s, reson: %s" % [ws.get_close_code(), ws.get_close_reason()])
|
||||
|
||||
|
||||
func _on_web_socket_client_connected_to_server():
|
||||
info("Client just connected with protocol: %s" % _client.get_socket().get_selected_protocol())
|
||||
|
||||
|
||||
func _on_web_socket_client_message_received(message):
|
||||
info("%s" % message)
|
||||
|
||||
|
||||
# UI signals.
|
||||
func _on_send_pressed():
|
||||
if _line_edit.text == "":
|
||||
return
|
||||
|
||||
info("Sending message: %s" % [_line_edit.text])
|
||||
_client.send(_line_edit.text)
|
||||
_line_edit.text = ""
|
||||
|
||||
|
||||
func _on_connect_toggled(pressed):
|
||||
if not pressed:
|
||||
_client.close()
|
||||
return
|
||||
if _host.text == "":
|
||||
return
|
||||
info("Connecting to host: %s." % [_host.text])
|
||||
var err = _client.connect_to_url(_host.text)
|
||||
if err != OK:
|
||||
info("Error connecting to host: %s" % [_host.text])
|
||||
return
|
||||
59
networking/websocket_chat/client.tscn
Normal file
59
networking/websocket_chat/client.tscn
Normal file
@@ -0,0 +1,59 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://ph5ghsflqegf"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://cyvrywci15kev" path="res://chat.tscn" id="1_cfcun"]
|
||||
[ext_resource type="Script" path="res://websocket/WebSocketClient.gd" id="2_m4g4y"]
|
||||
[ext_resource type="Script" path="res://client.gd" id="2_opbid"]
|
||||
|
||||
[node name="Client" instance=ExtResource("1_cfcun")]
|
||||
script = ExtResource("2_opbid")
|
||||
|
||||
[node name="WebSocketClient" type="Node" parent="." index="0"]
|
||||
script = ExtResource("2_m4g4y")
|
||||
supported_protocols = PackedStringArray("demo-chat")
|
||||
|
||||
[node name="Panel" parent="." index="1"]
|
||||
layout_mode = 1
|
||||
|
||||
[node name="VBoxContainer" parent="Panel" index="0"]
|
||||
layout_mode = 1
|
||||
|
||||
[node name="Listen" parent="Panel/VBoxContainer" index="0"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Connect" parent="Panel/VBoxContainer" index="1"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Host" parent="Panel/VBoxContainer/Connect" index="0"]
|
||||
layout_mode = 2
|
||||
offset_right = 1076.0
|
||||
|
||||
[node name="Connect" parent="Panel/VBoxContainer/Connect" index="1"]
|
||||
layout_mode = 2
|
||||
offset_left = 1080.0
|
||||
offset_right = 1152.0
|
||||
|
||||
[node name="Port" parent="Panel/VBoxContainer/Connect" index="2"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Listen" parent="Panel/VBoxContainer/Connect" index="3"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Send" parent="Panel/VBoxContainer" index="2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LineEdit" parent="Panel/VBoxContainer/Send" index="0"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Send" parent="Panel/VBoxContainer/Send" index="1"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RichTextLabel" parent="Panel/VBoxContainer" index="3"]
|
||||
layout_mode = 2
|
||||
|
||||
[connection signal="connected_to_server" from="WebSocketClient" to="." method="_on_web_socket_client_connected_to_server"]
|
||||
[connection signal="connection_closed" from="WebSocketClient" to="." method="_on_web_socket_client_connection_closed"]
|
||||
[connection signal="message_received" from="WebSocketClient" to="." method="_on_web_socket_client_message_received"]
|
||||
[connection signal="toggled" from="Panel/VBoxContainer/Connect/Connect" to="." method="_on_connect_toggled"]
|
||||
[connection signal="pressed" from="Panel/VBoxContainer/Send/Send" to="." method="_on_send_pressed"]
|
||||
@@ -1,84 +0,0 @@
|
||||
extends Node
|
||||
|
||||
@onready var _log_dest = get_parent().get_node(^"Panel/VBoxContainer/RichTextLabel")
|
||||
|
||||
var _client = WebSocketClient.new()
|
||||
var _write_mode = WebSocketPeer.WRITE_MODE_BINARY
|
||||
var _use_multiplayer = true
|
||||
var last_connected_client = 0
|
||||
|
||||
func _init():
|
||||
_client.connect(&"connection_established", self._client_connected)
|
||||
_client.connect(&"connection_error", self._client_disconnected)
|
||||
_client.connect(&"connection_closed", self._client_disconnected)
|
||||
_client.connect(&"server_close_request", self._client_close_request)
|
||||
_client.connect(&"data_received", self._client_received)
|
||||
|
||||
_client.connect(&"peer_packet", self._client_received)
|
||||
_client.connect(&"peer_connected", self._peer_connected)
|
||||
_client.connect(&"connection_succeeded", self._client_connected, ["multiplayer_protocol"])
|
||||
_client.connect(&"connection_failed", self._client_disconnected)
|
||||
|
||||
|
||||
func _client_close_request(code, reason):
|
||||
Utils._log(_log_dest, "Close code: %d, reason: %s" % [code, reason])
|
||||
|
||||
|
||||
func _peer_connected(id):
|
||||
Utils._log(_log_dest, "%s: Client just connected" % id)
|
||||
last_connected_client = id
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
_client.disconnect_from_host(1001, "Bye bye!")
|
||||
|
||||
|
||||
func _process(_delta):
|
||||
if _client.get_connection_status() == WebSocketClient.CONNECTION_DISCONNECTED:
|
||||
return
|
||||
|
||||
_client.poll()
|
||||
|
||||
|
||||
func _client_connected(protocol):
|
||||
Utils._log(_log_dest, "Client just connected with protocol: %s" % protocol)
|
||||
_client.get_peer(1).set_write_mode(_write_mode)
|
||||
|
||||
|
||||
func _client_disconnected(clean=true):
|
||||
Utils._log(_log_dest, "Client just disconnected. Was clean: %s" % clean)
|
||||
|
||||
|
||||
func _client_received(_p_id = 1):
|
||||
if _use_multiplayer:
|
||||
var peer_id = _client.get_packet_peer()
|
||||
var packet = _client.get_packet()
|
||||
Utils._log(_log_dest, "MPAPI: From %s Data: %s" % [str(peer_id), Utils.decode_data(packet, false)])
|
||||
else:
|
||||
var packet = _client.get_peer(1).get_packet()
|
||||
var is_string = _client.get_peer(1).was_string_packet()
|
||||
Utils._log(_log_dest, "Received data. BINARY: %s: %s" % [not is_string, Utils.decode_data(packet, is_string)])
|
||||
|
||||
|
||||
func connect_to_url(host, protocols, multiplayer):
|
||||
_use_multiplayer = multiplayer
|
||||
if _use_multiplayer:
|
||||
_write_mode = WebSocketPeer.WRITE_MODE_BINARY
|
||||
return _client.connect_to_url(host, protocols, multiplayer)
|
||||
|
||||
|
||||
func disconnect_from_host():
|
||||
_client.disconnect_from_host(1000, "Bye bye!")
|
||||
|
||||
|
||||
func send_data(data, dest):
|
||||
_client.get_peer(1).set_write_mode(_write_mode)
|
||||
if _use_multiplayer:
|
||||
_client.set_target_peer(dest)
|
||||
_client.put_packet(Utils.encode_data(data, _write_mode))
|
||||
else:
|
||||
_client.get_peer(1).put_packet(Utils.encode_data(data, _write_mode))
|
||||
|
||||
|
||||
func set_write_mode(mode):
|
||||
_write_mode = mode
|
||||
@@ -1,89 +0,0 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://client/client_ui.gd" type="Script" id=1]
|
||||
[ext_resource path="res://client/client.gd" type="Script" id=2]
|
||||
|
||||
[node name="Client" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="Connect" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 24.0
|
||||
|
||||
[node name="Host" type="LineEdit" parent="Panel/VBoxContainer/Connect"]
|
||||
offset_right = 956.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
text = "ws://localhost:8000/test/"
|
||||
placeholder_text = "ws://my.server/path/"
|
||||
|
||||
[node name="Connect" type="Button" parent="Panel/VBoxContainer/Connect"]
|
||||
offset_left = 960.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 24.0
|
||||
toggle_mode = true
|
||||
text = "Connect"
|
||||
|
||||
[node name="Settings" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_top = 28.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 52.0
|
||||
|
||||
[node name="Mode" type="OptionButton" parent="Panel/VBoxContainer/Settings"]
|
||||
offset_right = 29.0
|
||||
offset_bottom = 24.0
|
||||
|
||||
[node name="Multiplayer" type="CheckBox" parent="Panel/VBoxContainer/Settings"]
|
||||
offset_left = 33.0
|
||||
offset_right = 159.0
|
||||
offset_bottom = 24.0
|
||||
pressed = true
|
||||
text = "Multiplayer API"
|
||||
|
||||
[node name="Destination" type="OptionButton" parent="Panel/VBoxContainer/Settings"]
|
||||
offset_left = 163.0
|
||||
offset_right = 192.0
|
||||
offset_bottom = 24.0
|
||||
|
||||
[node name="Send" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_top = 56.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 80.0
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Panel/VBoxContainer/Send"]
|
||||
offset_right = 977.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Enter some text to send..."
|
||||
|
||||
[node name="Send" type="Button" parent="Panel/VBoxContainer/Send"]
|
||||
offset_left = 981.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 24.0
|
||||
text = "Send"
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="Panel/VBoxContainer"]
|
||||
offset_top = 84.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 600.0
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Client" type="Node" parent="."]
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[connection signal="toggled" from="Panel/VBoxContainer/Connect/Connect" to="." method="_on_Connect_toggled"]
|
||||
[connection signal="item_selected" from="Panel/VBoxContainer/Settings/Mode" to="." method="_on_Mode_item_selected"]
|
||||
[connection signal="pressed" from="Panel/VBoxContainer/Send/Send" to="." method="_on_Send_pressed"]
|
||||
@@ -1,62 +0,0 @@
|
||||
extends Control
|
||||
|
||||
@onready var _client = $Client
|
||||
@onready var _log_dest = $Panel/VBoxContainer/RichTextLabel
|
||||
@onready var _line_edit = $Panel/VBoxContainer/Send/LineEdit
|
||||
@onready var _host = $Panel/VBoxContainer/Connect/Host
|
||||
@onready var _multiplayer = $Panel/VBoxContainer/Settings/Multiplayer
|
||||
@onready var _write_mode = $Panel/VBoxContainer/Settings/Mode
|
||||
@onready var _destination = $Panel/VBoxContainer/Settings/Destination
|
||||
|
||||
func _ready():
|
||||
_write_mode.clear()
|
||||
_write_mode.add_item("BINARY")
|
||||
_write_mode.set_item_metadata(0, WebSocketPeer.WRITE_MODE_BINARY)
|
||||
_write_mode.add_item("TEXT")
|
||||
_write_mode.set_item_metadata(1, WebSocketPeer.WRITE_MODE_TEXT)
|
||||
|
||||
_destination.add_item("Broadcast")
|
||||
_destination.set_item_metadata(0, 0)
|
||||
_destination.add_item("Last connected")
|
||||
_destination.set_item_metadata(1, 1)
|
||||
_destination.add_item("All But last connected")
|
||||
_destination.set_item_metadata(2, -1)
|
||||
_destination.select(0)
|
||||
|
||||
|
||||
func _on_Mode_item_selected(_id):
|
||||
_client.set_write_mode(_write_mode.get_selected_metadata())
|
||||
|
||||
|
||||
func _on_Send_pressed():
|
||||
if _line_edit.text == "":
|
||||
return
|
||||
|
||||
var dest = _destination.get_selected_metadata()
|
||||
if dest > 0:
|
||||
dest = _client.last_connected_client
|
||||
elif dest < 0:
|
||||
dest = -_client.last_connected_client
|
||||
|
||||
Utils._log(_log_dest, "Sending data %s to %s" % [_line_edit.text, dest])
|
||||
_client.send_data(_line_edit.text, dest)
|
||||
_line_edit.text = ""
|
||||
|
||||
|
||||
func _on_Connect_toggled( pressed ):
|
||||
if pressed:
|
||||
var multiplayer = _multiplayer.pressed
|
||||
if multiplayer:
|
||||
_write_mode.disabled = true
|
||||
else:
|
||||
_destination.disabled = true
|
||||
_multiplayer.disabled = true
|
||||
if _host.text != "":
|
||||
Utils._log(_log_dest, "Connecting to host: %s" % [_host.text])
|
||||
var supported_protocols = PackedStringArray(["my-protocol2", "my-protocol", "binary"])
|
||||
_client.connect_to_url(_host.text, supported_protocols, multiplayer)
|
||||
else:
|
||||
_destination.disabled = false
|
||||
_write_mode.disabled = false
|
||||
_multiplayer.disabled = false
|
||||
_client.disconnect_from_host()
|
||||
56
networking/websocket_chat/combo.tscn
Normal file
56
networking/websocket_chat/combo.tscn
Normal file
@@ -0,0 +1,56 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dye16x7udqrxg"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://qvg4q16blgx5" path="res://server.tscn" id="1_0srxc"]
|
||||
[ext_resource type="PackedScene" uid="uid://ph5ghsflqegf" path="res://client.tscn" id="2_percb"]
|
||||
|
||||
[node name="Combo" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="Box" type="HBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Server" parent="Box" instance=ExtResource("1_0srxc")]
|
||||
anchors_preset = 0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_right = 574.0
|
||||
offset_bottom = 648.0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Box"]
|
||||
offset_left = 578.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 648.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Client" parent="Box/VBoxContainer" instance=ExtResource("2_percb")]
|
||||
anchors_preset = 0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_right = 574.0
|
||||
offset_bottom = 213.0
|
||||
|
||||
[node name="Client2" parent="Box/VBoxContainer" instance=ExtResource("2_percb")]
|
||||
anchors_preset = 0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_top = 217.0
|
||||
offset_right = 574.0
|
||||
offset_bottom = 430.0
|
||||
|
||||
[node name="Client3" parent="Box/VBoxContainer" instance=ExtResource("2_percb")]
|
||||
anchors_preset = 0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_top = 434.0
|
||||
offset_right = 574.0
|
||||
offset_bottom = 648.0
|
||||
@@ -1,53 +0,0 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://server/server.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://client/client.tscn" type="PackedScene" id=2]
|
||||
|
||||
[node name="Combo" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="Box" type="HBoxContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
custom_constants/separation = 20
|
||||
|
||||
[node name="ServerControl" parent="Box" instance=ExtResource( 1 )]
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_right = 502.0
|
||||
offset_bottom = 600.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Box"]
|
||||
offset_left = 522.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 600.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Client" parent="Box/VBoxContainer" instance=ExtResource( 2 )]
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_right = 502.0
|
||||
offset_bottom = 197.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Client2" parent="Box/VBoxContainer" instance=ExtResource( 2 )]
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_top = 201.0
|
||||
offset_right = 502.0
|
||||
offset_bottom = 398.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Client3" parent="Box/VBoxContainer" instance=ExtResource( 2 )]
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_top = 402.0
|
||||
offset_right = 502.0
|
||||
offset_bottom = 600.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
@@ -1,8 +1,9 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture2D"
|
||||
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://db0a1leye11ap"
|
||||
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
@@ -10,26 +11,24 @@ metadata={
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"]
|
||||
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/hdr_compression=1
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
||||
@@ -6,19 +6,32 @@
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=4
|
||||
config_version=5
|
||||
|
||||
_global_script_classes=[{
|
||||
"base": "Node",
|
||||
"class": &"WebSocketClient",
|
||||
"language": &"GDScript",
|
||||
"path": "res://websocket/WebSocketClient.gd"
|
||||
}, {
|
||||
"base": "Node",
|
||||
"class": &"WebSocketServer",
|
||||
"language": &"GDScript",
|
||||
"path": "res://websocket/WebSocketServer.gd"
|
||||
}]
|
||||
_global_script_class_icons={
|
||||
"WebSocketClient": "",
|
||||
"WebSocketServer": ""
|
||||
}
|
||||
|
||||
[application]
|
||||
|
||||
config/name="WebSocket Chat Demo"
|
||||
config/description="This is a demo of a simple chat implemented using WebSockets, showing both how to host a websocket server from Godot and how to connect to it."
|
||||
run/main_scene="res://combo/combo.tscn"
|
||||
run/main_scene="res://combo.tscn"
|
||||
config/features=PackedStringArray("4.0")
|
||||
config/icon="res://icon.png"
|
||||
|
||||
[autoload]
|
||||
|
||||
Utils="*res://utils.gd"
|
||||
|
||||
[gdnative]
|
||||
|
||||
singletons=[]
|
||||
|
||||
51
networking/websocket_chat/server.gd
Normal file
51
networking/websocket_chat/server.gd
Normal file
@@ -0,0 +1,51 @@
|
||||
extends Control
|
||||
|
||||
@onready var _server : WebSocketServer = $WebSocketServer
|
||||
@onready var _log_dest = $Panel/VBoxContainer/RichTextLabel
|
||||
@onready var _line_edit = $Panel/VBoxContainer/Send/LineEdit
|
||||
@onready var _listen_port = $Panel/VBoxContainer/Connect/Port
|
||||
|
||||
func info(msg):
|
||||
print(msg)
|
||||
_log_dest.add_text(str(msg) + "\n")
|
||||
|
||||
|
||||
# Server signals
|
||||
func _on_web_socket_server_client_connected(peer_id):
|
||||
var peer : WebSocketPeer = _server.peers[peer_id]
|
||||
info("Remote client connected: %d. Protocol: %s" % [peer_id, peer.get_selected_protocol()])
|
||||
_server.send(-peer_id, "[%d] connected" % peer_id)
|
||||
|
||||
|
||||
func _on_web_socket_server_client_disconnected(peer_id):
|
||||
var peer : WebSocketPeer = _server.peers[peer_id]
|
||||
info("Remote client disconnected: %d. Code: %d, Reason: %s" % [peer_id, peer.get_close_code(), peer.get_close_reason()])
|
||||
_server.send(-peer_id, "[%d] disconnected" % peer_id)
|
||||
|
||||
|
||||
func _on_web_socket_server_message_received(peer_id, message):
|
||||
info("Server received data from peer %d: %s" % [peer_id, message])
|
||||
_server.send(-peer_id, "[%d] Says: %s" % [peer_id, message])
|
||||
|
||||
|
||||
# UI signals.
|
||||
func _on_send_pressed():
|
||||
if _line_edit.text == "":
|
||||
return
|
||||
|
||||
info("Sending message: %s" % [_line_edit.text])
|
||||
_server.send(0, "Server says: %s" % _line_edit.text)
|
||||
_line_edit.text = ""
|
||||
|
||||
|
||||
func _on_listen_toggled(pressed):
|
||||
if not pressed:
|
||||
_server.stop()
|
||||
info("Server stopped")
|
||||
return
|
||||
var port = int(_listen_port.value)
|
||||
var err = _server.listen(port)
|
||||
if err != OK:
|
||||
info("Error listing on port %s" % port)
|
||||
return
|
||||
info("Listing on port %s, supported protocols: %s" % [port, _server.supported_protocols])
|
||||
61
networking/websocket_chat/server.tscn
Normal file
61
networking/websocket_chat/server.tscn
Normal file
@@ -0,0 +1,61 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://qvg4q16blgx5"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://cyvrywci15kev" path="res://chat.tscn" id="1_i673i"]
|
||||
[ext_resource type="Script" path="res://server.gd" id="1_urpfw"]
|
||||
[ext_resource type="Script" path="res://websocket/WebSocketServer.gd" id="3_0eqsy"]
|
||||
|
||||
[node name="Server" instance=ExtResource("1_i673i")]
|
||||
script = ExtResource("1_urpfw")
|
||||
|
||||
[node name="WebSocketServer" type="Node" parent="." index="0"]
|
||||
script = ExtResource("3_0eqsy")
|
||||
supported_protocols = PackedStringArray("demo-chat")
|
||||
|
||||
[node name="Panel" parent="." index="1"]
|
||||
layout_mode = 3
|
||||
|
||||
[node name="VBoxContainer" parent="Panel" index="0"]
|
||||
layout_mode = 3
|
||||
|
||||
[node name="Listen" parent="Panel/VBoxContainer" index="0"]
|
||||
layout_mode = 3
|
||||
|
||||
[node name="Connect" parent="Panel/VBoxContainer" index="1"]
|
||||
layout_mode = 3
|
||||
|
||||
[node name="Host" parent="Panel/VBoxContainer/Connect" index="0"]
|
||||
visible = false
|
||||
layout_mode = 3
|
||||
offset_right = 1006.0
|
||||
|
||||
[node name="Connect" parent="Panel/VBoxContainer/Connect" index="1"]
|
||||
visible = false
|
||||
layout_mode = 3
|
||||
|
||||
[node name="Port" parent="Panel/VBoxContainer/Connect" index="2"]
|
||||
layout_mode = 3
|
||||
offset_left = 0.0
|
||||
offset_right = 83.0
|
||||
|
||||
[node name="Listen" parent="Panel/VBoxContainer/Connect" index="3"]
|
||||
layout_mode = 3
|
||||
offset_left = 87.0
|
||||
offset_right = 142.0
|
||||
|
||||
[node name="Send" parent="Panel/VBoxContainer" index="2"]
|
||||
layout_mode = 3
|
||||
|
||||
[node name="LineEdit" parent="Panel/VBoxContainer/Send" index="0"]
|
||||
layout_mode = 3
|
||||
|
||||
[node name="Send" parent="Panel/VBoxContainer/Send" index="1"]
|
||||
layout_mode = 3
|
||||
|
||||
[node name="RichTextLabel" parent="Panel/VBoxContainer" index="3"]
|
||||
layout_mode = 3
|
||||
|
||||
[connection signal="client_connected" from="WebSocketServer" to="." method="_on_web_socket_server_client_connected"]
|
||||
[connection signal="client_disconnected" from="WebSocketServer" to="." method="_on_web_socket_server_client_disconnected"]
|
||||
[connection signal="message_received" from="WebSocketServer" to="." method="_on_web_socket_server_message_received"]
|
||||
[connection signal="toggled" from="Panel/VBoxContainer/Connect/Listen" to="." method="_on_listen_toggled"]
|
||||
[connection signal="pressed" from="Panel/VBoxContainer/Send/Send" to="." method="_on_send_pressed"]
|
||||
@@ -1,84 +0,0 @@
|
||||
extends Node
|
||||
|
||||
@onready var _log_dest = get_parent().get_node(^"Panel/VBoxContainer/RichTextLabel")
|
||||
|
||||
var _server = WebSocketServer.new()
|
||||
var _clients = {}
|
||||
var _write_mode = WebSocketPeer.WRITE_MODE_BINARY
|
||||
var _use_multiplayer = true
|
||||
var last_connected_client = 0
|
||||
|
||||
func _init():
|
||||
_server.connect(&"client_connected", self._client_connected)
|
||||
_server.connect(&"client_disconnected", self._client_disconnected)
|
||||
_server.connect(&"client_close_request", self._client_close_request)
|
||||
_server.connect(&"data_received", self._client_receive)
|
||||
|
||||
_server.connect(&"peer_packet", self._client_receive)
|
||||
_server.connect(&"peer_connected", self._client_connected, ["multiplayer_protocol"])
|
||||
_server.connect(&"peer_disconnected", self._client_disconnected)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
_clients.clear()
|
||||
_server.stop()
|
||||
|
||||
|
||||
func _process(_delta):
|
||||
if _server.is_listening():
|
||||
_server.poll()
|
||||
|
||||
|
||||
func _client_close_request(id, code, reason):
|
||||
print(reason == "Bye bye!")
|
||||
Utils._log(_log_dest, "Client %s close code: %d, reason: %s" % [id, code, reason])
|
||||
|
||||
|
||||
func _client_connected(id, protocol):
|
||||
_clients[id] = _server.get_peer(id)
|
||||
_clients[id].set_write_mode(_write_mode)
|
||||
last_connected_client = id
|
||||
Utils._log(_log_dest, "%s: Client connected with protocol %s" % [id, protocol])
|
||||
|
||||
|
||||
func _client_disconnected(id, clean = true):
|
||||
Utils._log(_log_dest, "Client %s disconnected. Was clean: %s" % [id, clean])
|
||||
if _clients.has(id):
|
||||
_clients.erase(id)
|
||||
|
||||
|
||||
func _client_receive(id):
|
||||
if _use_multiplayer:
|
||||
var peer_id = _server.get_packet_peer()
|
||||
var packet = _server.get_packet()
|
||||
Utils._log(_log_dest, "MPAPI: From %s data: %s" % [peer_id, Utils.decode_data(packet, false)])
|
||||
else:
|
||||
var packet = _server.get_peer(id).get_packet()
|
||||
var is_string = _server.get_peer(id).was_string_packet()
|
||||
Utils._log(_log_dest, "Data from %s BINARY: %s: %s" % [id, not is_string, Utils.decode_data(packet, is_string)])
|
||||
|
||||
|
||||
func send_data(data, dest):
|
||||
if _use_multiplayer:
|
||||
_server.set_target_peer(dest)
|
||||
_server.put_packet(Utils.encode_data(data, _write_mode))
|
||||
else:
|
||||
for id in _clients:
|
||||
_server.get_peer(id).put_packet(Utils.encode_data(data, _write_mode))
|
||||
|
||||
|
||||
func listen(port, supported_protocols, multiplayer):
|
||||
_use_multiplayer = multiplayer
|
||||
if _use_multiplayer:
|
||||
set_write_mode(WebSocketPeer.WRITE_MODE_BINARY)
|
||||
return _server.listen(port, supported_protocols, multiplayer)
|
||||
|
||||
|
||||
func stop():
|
||||
_server.stop()
|
||||
|
||||
|
||||
func set_write_mode(mode):
|
||||
_write_mode = mode
|
||||
for c in _clients:
|
||||
_clients[c].set_write_mode(_write_mode)
|
||||
@@ -1,88 +0,0 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://server/server_ui.gd" type="Script" id=1]
|
||||
[ext_resource path="res://server/server.gd" type="Script" id=2]
|
||||
|
||||
[node name="ServerControl" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Server" type="Node" parent="."]
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 24.0
|
||||
|
||||
[node name="Port" type="SpinBox" parent="Panel/VBoxContainer/HBoxContainer"]
|
||||
offset_right = 74.0
|
||||
offset_bottom = 24.0
|
||||
min_value = 1.0
|
||||
max_value = 65535.0
|
||||
value = 8000.0
|
||||
|
||||
[node name="Listen" type="Button" parent="Panel/VBoxContainer/HBoxContainer"]
|
||||
offset_left = 78.0
|
||||
offset_right = 129.0
|
||||
offset_bottom = 24.0
|
||||
toggle_mode = true
|
||||
text = "Listen"
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_top = 28.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 52.0
|
||||
|
||||
[node name="WriteMode" type="OptionButton" parent="Panel/VBoxContainer/HBoxContainer2"]
|
||||
offset_right = 29.0
|
||||
offset_bottom = 24.0
|
||||
|
||||
[node name="MPAPI" type="CheckBox" parent="Panel/VBoxContainer/HBoxContainer2"]
|
||||
offset_left = 33.0
|
||||
offset_right = 159.0
|
||||
offset_bottom = 24.0
|
||||
pressed = true
|
||||
text = "Multiplayer API"
|
||||
|
||||
[node name="Destination" type="OptionButton" parent="Panel/VBoxContainer/HBoxContainer2"]
|
||||
offset_left = 163.0
|
||||
offset_right = 192.0
|
||||
offset_bottom = 24.0
|
||||
|
||||
[node name="HBoxContainer3" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
offset_top = 56.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 80.0
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer3"]
|
||||
offset_right = 977.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Send" type="Button" parent="Panel/VBoxContainer/HBoxContainer3"]
|
||||
offset_left = 981.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 24.0
|
||||
text = "Send"
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="Panel/VBoxContainer"]
|
||||
offset_top = 84.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 600.0
|
||||
size_flags_vertical = 3
|
||||
|
||||
[connection signal="toggled" from="Panel/VBoxContainer/HBoxContainer/Listen" to="." method="_on_Listen_toggled"]
|
||||
[connection signal="item_selected" from="Panel/VBoxContainer/HBoxContainer2/WriteMode" to="." method="_on_WriteMode_item_selected"]
|
||||
[connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer3/Send" to="." method="_on_Send_pressed"]
|
||||
@@ -1,70 +0,0 @@
|
||||
extends Control
|
||||
|
||||
@onready var _server = $Server
|
||||
@onready var _port = $Panel/VBoxContainer/HBoxContainer/Port
|
||||
@onready var _line_edit = $Panel/VBoxContainer/HBoxContainer3/LineEdit
|
||||
@onready var _write_mode = $Panel/VBoxContainer/HBoxContainer2/WriteMode
|
||||
@onready var _log_dest = $Panel/VBoxContainer/RichTextLabel
|
||||
@onready var _multiplayer = $Panel/VBoxContainer/HBoxContainer2/MPAPI
|
||||
@onready var _destination = $Panel/VBoxContainer/HBoxContainer2/Destination
|
||||
|
||||
func _ready():
|
||||
_write_mode.clear()
|
||||
_write_mode.add_item("BINARY")
|
||||
_write_mode.set_item_metadata(0, WebSocketPeer.WRITE_MODE_BINARY)
|
||||
_write_mode.add_item("TEXT")
|
||||
_write_mode.set_item_metadata(1, WebSocketPeer.WRITE_MODE_TEXT)
|
||||
_write_mode.select(0)
|
||||
|
||||
_destination.add_item("Broadcast")
|
||||
_destination.set_item_metadata(0, 0)
|
||||
_destination.add_item("Last connected")
|
||||
_destination.set_item_metadata(1, 1)
|
||||
_destination.add_item("All But last connected")
|
||||
_destination.set_item_metadata(2, -1)
|
||||
_destination.select(0)
|
||||
|
||||
|
||||
func _on_Listen_toggled(pressed):
|
||||
if pressed:
|
||||
var use_multiplayer = _multiplayer.pressed
|
||||
_multiplayer.disabled = true
|
||||
var supported_protocols = PackedStringArray(["my-protocol", "binary"])
|
||||
var port = int(_port.value)
|
||||
if use_multiplayer:
|
||||
_write_mode.disabled = true
|
||||
_write_mode.select(0)
|
||||
else:
|
||||
_destination.disabled = true
|
||||
_destination.select(0)
|
||||
if _server.listen(port, supported_protocols, use_multiplayer) == OK:
|
||||
Utils._log(_log_dest, "Listing on port %s" % port)
|
||||
if not use_multiplayer:
|
||||
Utils._log(_log_dest, "Supported protocols: %s" % supported_protocols)
|
||||
else:
|
||||
Utils._log(_log_dest, "Error listening on port %s" % port)
|
||||
else:
|
||||
_server.stop()
|
||||
_multiplayer.disabled = false
|
||||
_write_mode.disabled = false
|
||||
_destination.disabled = false
|
||||
Utils._log(_log_dest, "Server stopped")
|
||||
|
||||
|
||||
func _on_Send_pressed():
|
||||
if _line_edit.text == "":
|
||||
return
|
||||
|
||||
var dest = _destination.get_selected_metadata()
|
||||
if dest > 0:
|
||||
dest = _server.last_connected_client
|
||||
elif dest < 0:
|
||||
dest = -_server.last_connected_client
|
||||
|
||||
Utils._log(_log_dest, "Sending data %s to %s" % [_line_edit.text, dest])
|
||||
_server.send_data(_line_edit.text, dest)
|
||||
_line_edit.text = ""
|
||||
|
||||
|
||||
func _on_WriteMode_item_selected(_id):
|
||||
_server.set_write_mode(_write_mode.get_selected_metadata())
|
||||
@@ -1,17 +0,0 @@
|
||||
extends Node
|
||||
|
||||
func encode_data(data, mode):
|
||||
if mode == WebSocketPeer.WRITE_MODE_TEXT:
|
||||
return data.to_utf8()
|
||||
return var2bytes(data)
|
||||
|
||||
|
||||
func decode_data(data, is_string):
|
||||
if is_string:
|
||||
return data.get_string_from_utf8()
|
||||
return bytes2var(data)
|
||||
|
||||
|
||||
func _log(node, msg):
|
||||
print(msg)
|
||||
node.add_text(str(msg) + "\n")
|
||||
73
networking/websocket_chat/websocket/WebSocketClient.gd
Normal file
73
networking/websocket_chat/websocket/WebSocketClient.gd
Normal file
@@ -0,0 +1,73 @@
|
||||
extends Node
|
||||
class_name WebSocketClient
|
||||
|
||||
@export var handshake_headers : PackedStringArray
|
||||
@export var supported_protocols : PackedStringArray
|
||||
@export var tls_trusted_certificate : X509Certificate
|
||||
@export var tls_verify := true
|
||||
|
||||
|
||||
var socket = WebSocketPeer.new()
|
||||
var last_state = WebSocketPeer.STATE_CLOSED
|
||||
|
||||
|
||||
signal connected_to_server()
|
||||
signal connection_closed()
|
||||
signal message_received(message: Variant)
|
||||
|
||||
|
||||
func connect_to_url(url) -> int:
|
||||
socket.supported_protocols = supported_protocols
|
||||
socket.handshake_headers = handshake_headers
|
||||
var err = socket.connect_to_url(url, tls_verify, tls_trusted_certificate)
|
||||
if err != OK:
|
||||
return err
|
||||
last_state = socket.get_ready_state()
|
||||
return OK
|
||||
|
||||
|
||||
func send(message) -> int:
|
||||
if typeof(message) == TYPE_STRING:
|
||||
return socket.send_text(message)
|
||||
return socket.send(var_to_bytes(message))
|
||||
|
||||
|
||||
func get_message() -> Variant:
|
||||
if socket.get_available_packet_count() < 1:
|
||||
return null
|
||||
var pkt = socket.get_packet()
|
||||
if socket.was_string_packet():
|
||||
return pkt.get_string_from_utf8()
|
||||
return bytes_to_var(pkt)
|
||||
|
||||
|
||||
func close(code := 1000, reason := "") -> void:
|
||||
socket.close(code, reason)
|
||||
last_state = socket.get_ready_state()
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
socket = WebSocketPeer.new()
|
||||
last_state = socket.get_ready_state()
|
||||
|
||||
|
||||
func get_socket() -> WebSocketPeer:
|
||||
return socket
|
||||
|
||||
|
||||
func poll() -> void:
|
||||
if socket.get_ready_state() != socket.STATE_CLOSED:
|
||||
socket.poll()
|
||||
var state = socket.get_ready_state()
|
||||
if last_state != state:
|
||||
last_state = state
|
||||
if state == socket.STATE_OPEN:
|
||||
connected_to_server.emit()
|
||||
elif state == socket.STATE_CLOSED:
|
||||
connection_closed.emit()
|
||||
while socket.get_ready_state() == socket.STATE_OPEN and socket.get_available_packet_count():
|
||||
message_received.emit(get_message())
|
||||
|
||||
|
||||
func _process(delta):
|
||||
poll()
|
||||
162
networking/websocket_chat/websocket/WebSocketServer.gd
Normal file
162
networking/websocket_chat/websocket/WebSocketServer.gd
Normal file
@@ -0,0 +1,162 @@
|
||||
extends Node
|
||||
class_name WebSocketServer
|
||||
|
||||
signal message_received(peer_id : int, message)
|
||||
signal client_connected(peer_id : int)
|
||||
signal client_disconnected(peer_id : int)
|
||||
|
||||
@export var handshake_headers := PackedStringArray()
|
||||
@export var supported_protocols : PackedStringArray
|
||||
@export var handshake_timout := 3000
|
||||
@export var use_tls := false
|
||||
@export var tls_cert : X509Certificate
|
||||
@export var tls_key : CryptoKey
|
||||
@export var refuse_new_connections := false :
|
||||
set(refuse):
|
||||
if refuse:
|
||||
pending_peers.clear()
|
||||
|
||||
|
||||
class PendingPeer:
|
||||
var connect_time : int
|
||||
var tcp : StreamPeerTCP
|
||||
var connection : StreamPeer
|
||||
var ws : WebSocketPeer
|
||||
|
||||
func _init(p_tcp: StreamPeerTCP):
|
||||
tcp = p_tcp
|
||||
connection = p_tcp
|
||||
connect_time = Time.get_ticks_msec()
|
||||
|
||||
|
||||
var tcp_server := TCPServer.new()
|
||||
var pending_peers : Array[PendingPeer] = []
|
||||
var peers : Dictionary
|
||||
|
||||
|
||||
func listen(port : int) -> int:
|
||||
assert(not tcp_server.is_listening())
|
||||
return tcp_server.listen(port)
|
||||
|
||||
|
||||
func stop():
|
||||
tcp_server.stop()
|
||||
pending_peers.clear()
|
||||
peers.clear()
|
||||
|
||||
|
||||
func send(peer_id, message) -> int:
|
||||
var type = typeof(message)
|
||||
if peer_id <= 0:
|
||||
# Send to multiple peers, (zero = brodcast, negative = exclude one)
|
||||
for id in peers:
|
||||
if id == -peer_id:
|
||||
continue
|
||||
if type == TYPE_STRING:
|
||||
peers[id].send_text(message)
|
||||
else:
|
||||
peers[id].put_packet(message)
|
||||
return OK
|
||||
|
||||
assert(peers.has(peer_id))
|
||||
var socket = peers[peer_id]
|
||||
if type == TYPE_STRING:
|
||||
return socket.send_text(message)
|
||||
return socket.send(var_to_bytes(message))
|
||||
|
||||
|
||||
func get_message(peer_id) -> Variant:
|
||||
assert(peers.has(peer_id))
|
||||
var socket = peers[peer_id]
|
||||
if socket.get_available_packet_count() < 1:
|
||||
return null
|
||||
var pkt = socket.get_packet()
|
||||
if socket.was_string_packet():
|
||||
return pkt.get_string_from_utf8()
|
||||
return bytes_to_var(pkt)
|
||||
|
||||
|
||||
func has_message(peer_id) -> bool:
|
||||
assert(peers.has(peer_id))
|
||||
return peers[peer_id].get_available_packet_count() > 0
|
||||
|
||||
|
||||
func _create_peer() -> WebSocketPeer:
|
||||
var ws = WebSocketPeer.new()
|
||||
ws.supported_protocols = supported_protocols
|
||||
ws.handshake_headers = handshake_headers
|
||||
return ws
|
||||
|
||||
|
||||
func poll() -> void:
|
||||
if not tcp_server.is_listening():
|
||||
return
|
||||
while not refuse_new_connections and tcp_server.is_connection_available():
|
||||
var conn = tcp_server.take_connection()
|
||||
assert(conn != null)
|
||||
pending_peers.append(PendingPeer.new(conn))
|
||||
var to_remove := []
|
||||
for p in pending_peers:
|
||||
if not _connect_pending(p):
|
||||
if p.connect_time + handshake_timout < Time.get_ticks_msec():
|
||||
# Timeout
|
||||
to_remove.append(p)
|
||||
continue # Still pending
|
||||
to_remove.append(p)
|
||||
for r in to_remove:
|
||||
pending_peers.erase(r)
|
||||
to_remove.clear()
|
||||
for id in peers:
|
||||
var p : WebSocketPeer = peers[id]
|
||||
var packets = p.get_available_packet_count()
|
||||
p.poll()
|
||||
if p.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
client_disconnected.emit(id)
|
||||
to_remove.append(id)
|
||||
continue
|
||||
while p.get_available_packet_count():
|
||||
message_received.emit(id, get_message(id))
|
||||
for r in to_remove:
|
||||
peers.erase(r)
|
||||
to_remove.clear()
|
||||
|
||||
|
||||
func _connect_pending(p: PendingPeer) -> bool:
|
||||
if p.ws != null:
|
||||
# Poll websocket client if doing handshake
|
||||
p.ws.poll()
|
||||
var state = p.ws.get_ready_state()
|
||||
if state == WebSocketPeer.STATE_OPEN:
|
||||
var id = randi_range(2, 1 << 30)
|
||||
peers[id] = p.ws
|
||||
client_connected.emit(id)
|
||||
return true # Success.
|
||||
elif state != WebSocketPeer.STATE_CONNECTING:
|
||||
return true # Failure.
|
||||
return false # Still connecting.
|
||||
elif p.tcp.get_status() != StreamPeerTCP.STATUS_CONNECTED:
|
||||
return true # TCP disconnected.
|
||||
elif not use_tls:
|
||||
# TCP is ready, create WS peer
|
||||
p.ws = _create_peer()
|
||||
p.ws.accept_stream(p.tcp)
|
||||
return false # WebSocketPeer connection is pending.
|
||||
else:
|
||||
if p.connection == p.tcp:
|
||||
assert(tls_key != null and tls_cert != null)
|
||||
var tls = StreamPeerTLS.new()
|
||||
tls.accept_stream(p.tcp, tls_key, tls_cert)
|
||||
p.connection = tls
|
||||
p.connection.poll()
|
||||
var status = p.connection.get_status()
|
||||
if status == StreamPeerTLS.STATUS_CONNECTED:
|
||||
p.ws = _create_peer()
|
||||
p.ws.accept_stream(p.connection)
|
||||
return false # WebSocketPeer connection is pending.
|
||||
if status != StreamPeerTLS.STATUS_HANDSHAKING:
|
||||
return true # Failure.
|
||||
return false
|
||||
|
||||
|
||||
func _process(delta):
|
||||
poll()
|
||||
Reference in New Issue
Block a user