From 27979ffe9f30a3cf19e1893583deb35f9a7fd0b3 Mon Sep 17 00:00:00 2001 From: denizk0461 Date: Sun, 29 Jun 2025 20:34:46 +0200 Subject: [PATCH] Switched from UDP to TCP-based connection; implemented creating, joining, leaving lobbies --- components/client_interface.gd | 61 +++++++++++ components/web_client.gd | 4 +- components/web_client_tcp.gd | 112 +++++++++++++++++++++ components/web_client_tcp.gd.uid | 1 + game.tscn | 45 ++++++--- game.tscn2646493547.tmp | 92 +++++++++++++++++ globals/tcp_messages.gd | 35 +++++++ globals/tcp_messages.gd.uid | 1 + project.godot | 2 +- server_tcp.py | 168 +++++++++++++++++++++++++++++++ 10 files changed, 503 insertions(+), 18 deletions(-) create mode 100644 components/web_client_tcp.gd create mode 100644 components/web_client_tcp.gd.uid create mode 100644 game.tscn2646493547.tmp create mode 100644 globals/tcp_messages.gd create mode 100644 globals/tcp_messages.gd.uid create mode 100644 server_tcp.py diff --git a/components/client_interface.gd b/components/client_interface.gd index 4771f29..b6cba89 100644 --- a/components/client_interface.gd +++ b/components/client_interface.gd @@ -1,2 +1,63 @@ class_name ClientInterface extends Control + +@export var client: WebClientTCP + +@export var connect_button: Button +@export var create_lobby_button: Button +@export var join_lobby_button: Button +@export var leave_lobby_button: Button + +@export var name_input: LineEdit +@export var lobby_code_input: LineEdit + +func _on_create_lobby_button_pressed() -> void: + client.create_lobby(name_input.text.strip_edges()) + + create_lobby_button.disabled = true + join_lobby_button.disabled = true + leave_lobby_button.disabled = false + + name_input.editable = false + lobby_code_input.editable = false + +func _on_connect_button_pressed() -> void: + connect_button.disabled = true + client.connect_to_server() + + create_lobby_button.disabled = true + join_lobby_button.disabled = true + leave_lobby_button.disabled = false + +func _on_join_lobby_button_pressed() -> void: + client.join_lobby(lobby_code_input.text.strip_edges(), name_input.text.strip_edges()) + +func _on_disconnect_lobby_button_pressed() -> void: + leave_lobby_button.disabled = true + client.leave_lobby() + +func _on_web_client_tcp_connected_to_server() -> void: + create_lobby_button.disabled = false + join_lobby_button.disabled = false + + name_input.editable = true + lobby_code_input.editable = true + +func _on_web_client_tcp_connection_error_occurred() -> void: + connect_button.disabled = false + +func _on_web_client_tcp_disconnected_from_server() -> void: + create_lobby_button.disabled = true + join_lobby_button.disabled = true + leave_lobby_button.disabled = true + + name_input.editable = true + lobby_code_input.editable = false + lobby_code_input.text = "" + + +func _on_web_client_tcp_connected_to_lobby(lobby_id: String) -> void: + lobby_code_input.text = lobby_id + +func _on_web_client_tcp_disconnected_from_lobby() -> void: + pass # Replace with function body. diff --git a/components/web_client.gd b/components/web_client.gd index 9a83ee6..9cf081f 100644 --- a/components/web_client.gd +++ b/components/web_client.gd @@ -8,12 +8,12 @@ extends Node @export var join_lobby_button: Button @export var disconnect_lobby_button: Button -var _peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new() +#var _peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new() var _address: String = "127.0.0.1" var _port: int = 9974 var _udp: PacketPeerUDP = PacketPeerUDP.new() -var _tcp = PacketPeerDTLS +var _stream := StreamPeerTCP.new() var _session_id: String var _client_id: String diff --git a/components/web_client_tcp.gd b/components/web_client_tcp.gd new file mode 100644 index 0000000..6916a15 --- /dev/null +++ b/components/web_client_tcp.gd @@ -0,0 +1,112 @@ +class_name WebClientTCP +extends Node + +var _tcp := StreamPeerTCP.new() + +var _new_status: int = -1 +var _processed_status: int = -1 + +var _lobby_id: String +var _client_id: String +var _client_name: String + +var received_data = "" + +var _thread: Thread +var _stop_thread: bool = false + +signal connected_to_server() +signal connection_error_occurred() +signal disconnected_from_server() + +signal connected_to_lobby(lobby_id: String) +signal disconnected_from_lobby() + +func _process(delta: float) -> void: + _tcp.poll() + _new_status = _tcp.get_status() + # Ensure status change is only processed once + if not _processed_status == _new_status: + match _new_status: + StreamPeerTCP.Status.STATUS_NONE: + pass + StreamPeerTCP.Status.STATUS_CONNECTING: + pass + StreamPeerTCP.Status.STATUS_CONNECTED: + _thread = Thread.new() + _thread.start(_listen_for_data) + connected_to_server.emit() + StreamPeerTCP.Status.STATUS_ERROR: + connection_error_occurred.emit() + + _processed_status = _new_status + + +func _listen_for_data() -> void: + while not _stop_thread: + #received_data += String.chr(_tcp.get_data(1)[0]) + received_data += _tcp.get_utf8_string(1) + print(received_data) + if received_data.ends_with("}"): + _handle_response(received_data) + received_data = "" + +func _handle_response(response: String) -> void: + var data = JSON.parse_string(response) + match data["response"]: + "create_lobby": + if data["status"] == "ok": + connected_to_lobby.emit.call_deferred(_lobby_id) + "join_lobby": + if data["status"] == "ok": + connected_to_lobby.emit.call_deferred(_lobby_id) + +func _exit_tree() -> void: + disconnect_from_server() + +func connect_to_server() -> void: + _tcp.connect_to_host("127.0.0.1", 9974) + _tcp.set_no_delay(true) + connected_to_server.emit() + +func disconnect_from_server() -> void: + _tcp.disconnect_from_host() + _stop_thread = true + if _thread: + _thread.wait_to_finish() + disconnected_from_server.emit() + +func create_lobby(client_name: String) -> void: + _lobby_id = _generate_code() + _client_id = _generate_code() + _client_name = client_name + + _send_message(TCPMessages.create_lobby(_lobby_id, _client_id, client_name)) + +func join_lobby(lobby_id: String, client_name: String) -> void: + _lobby_id = lobby_id + _client_id = _generate_code() + _client_name = client_name + + _send_message(TCPMessages.join_lobby(_lobby_id, _client_id, client_name)) + +func leave_lobby() -> void: + _send_message(TCPMessages.leave_lobby(_lobby_id, _client_id)) + + _lobby_id = "" + _client_id = "" + _client_name = "" + +func _send_message(message: String) -> void: + _tcp.put_data(message.to_utf8_buffer()) + +func _generate_code() -> String: + # use a local randomized RNG to keep the global RNG reproducible + var rng = RandomNumberGenerator.new() + rng.randomize() + var length = 6 + var result = '' + for _n in range(length): + var ascii = rng.randi_range(0, 25) + 65 + result += '%c' % ascii + return result diff --git a/components/web_client_tcp.gd.uid b/components/web_client_tcp.gd.uid new file mode 100644 index 0000000..4555df8 --- /dev/null +++ b/components/web_client_tcp.gd.uid @@ -0,0 +1 @@ +uid://p5gvnyhjqe6o diff --git a/game.tscn b/game.tscn index 1dbe397..895ecee 100644 --- a/game.tscn +++ b/game.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=3 uid="uid://gqrdpjslr4np"] -[ext_resource type="Script" uid="uid://bfaxxdr7v0emr" path="res://components/web_client.gd" id="1_80nbo"] +[ext_resource type="Script" uid="uid://p5gvnyhjqe6o" path="res://components/web_client_tcp.gd" id="1_feb5d"] [ext_resource type="Script" uid="uid://c0cqkatyo4uj1" path="res://components/client_interface.gd" id="2_e2o6t"] [sub_resource type="SystemFont" id="SystemFont_80nbo"] @@ -9,16 +9,10 @@ font_weight = 500 [node name="Game" type="Node"] -[node name="WebClient" type="Node" parent="." node_paths=PackedStringArray("name_input", "session_code_input", "output", "create_lobby_button", "join_lobby_button", "disconnect_lobby_button")] -script = ExtResource("1_80nbo") -name_input = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/NameInput") -session_code_input = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/SessionCodeInput") -output = NodePath("../ClientInterface/HBoxContainer/Output") -create_lobby_button = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/CreateLobbyButton") -join_lobby_button = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/JoinLobbyButton") -disconnect_lobby_button = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/DisconnectLobbyButton") +[node name="WebClientTCP" type="Node" parent="."] +script = ExtResource("1_feb5d") -[node name="ClientInterface" type="Control" parent="."] +[node name="ClientInterface" type="Control" parent="." node_paths=PackedStringArray("client", "connect_button", "create_lobby_button", "join_lobby_button", "leave_lobby_button", "name_input", "lobby_code_input")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -26,6 +20,13 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 script = ExtResource("2_e2o6t") +client = NodePath("../WebClientTCP") +connect_button = NodePath("HBoxContainer/VBoxContainer/ConnectButton") +create_lobby_button = NodePath("HBoxContainer/VBoxContainer/CreateLobbyButton") +join_lobby_button = NodePath("HBoxContainer/VBoxContainer/JoinLobbyButton") +leave_lobby_button = NodePath("HBoxContainer/VBoxContainer/LeaveLobbyButton") +name_input = NodePath("HBoxContainer/VBoxContainer/NameInput") +lobby_code_input = NodePath("HBoxContainer/VBoxContainer/LobbyCodeInput") [node name="ColorRect" type="ColorRect" parent="ClientInterface"] layout_mode = 1 @@ -45,23 +46,31 @@ offset_bottom = 1080.0 custom_minimum_size = Vector2(300, 0) layout_mode = 2 +[node name="ConnectButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +text = "Connect to Server" + [node name="NameInput" type="LineEdit" parent="ClientInterface/HBoxContainer/VBoxContainer"] layout_mode = 2 placeholder_text = "Your Name" +editable = false [node name="CreateLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] layout_mode = 2 +disabled = true text = "Create Lobby" -[node name="SessionCodeInput" type="LineEdit" parent="ClientInterface/HBoxContainer/VBoxContainer"] +[node name="LobbyCodeInput" type="LineEdit" parent="ClientInterface/HBoxContainer/VBoxContainer"] layout_mode = 2 placeholder_text = "Lobby Code" +editable = false [node name="JoinLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] layout_mode = 2 +disabled = true text = "Join Lobby" -[node name="DisconnectLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +[node name="LeaveLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] layout_mode = 2 disabled = true text = "Disconnect" @@ -74,6 +83,12 @@ size_flags_vertical = 1 theme_override_fonts/font = SubResource("SystemFont_80nbo") autowrap_mode = 2 -[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/CreateLobbyButton" to="WebClient" method="_on_create_lobby_button_pressed"] -[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/JoinLobbyButton" to="WebClient" method="_on_join_lobby_button_pressed"] -[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/DisconnectLobbyButton" to="WebClient" method="_on_disconnect_lobby_button_pressed"] +[connection signal="connected_to_lobby" from="WebClientTCP" to="ClientInterface" method="_on_web_client_tcp_connected_to_lobby"] +[connection signal="connected_to_server" from="WebClientTCP" to="ClientInterface" method="_on_web_client_tcp_connected_to_server"] +[connection signal="connection_error_occurred" from="WebClientTCP" to="ClientInterface" method="_on_web_client_tcp_connection_error_occurred"] +[connection signal="disconnected_from_lobby" from="WebClientTCP" to="ClientInterface" method="_on_web_client_tcp_disconnected_from_lobby"] +[connection signal="disconnected_from_server" from="WebClientTCP" to="ClientInterface" method="_on_web_client_tcp_disconnected_from_server"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/ConnectButton" to="ClientInterface" method="_on_connect_button_pressed"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/CreateLobbyButton" to="ClientInterface" method="_on_create_lobby_button_pressed"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/JoinLobbyButton" to="ClientInterface" method="_on_join_lobby_button_pressed"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/LeaveLobbyButton" to="ClientInterface" method="_on_disconnect_lobby_button_pressed"] diff --git a/game.tscn2646493547.tmp b/game.tscn2646493547.tmp new file mode 100644 index 0000000..23a3ef9 --- /dev/null +++ b/game.tscn2646493547.tmp @@ -0,0 +1,92 @@ +[gd_scene load_steps=4 format=3 uid="uid://gqrdpjslr4np"] + +[ext_resource type="Script" uid="uid://p5gvnyhjqe6o" path="res://components/web_client_tcp.gd" id="1_feb5d"] +[ext_resource type="Script" uid="uid://c0cqkatyo4uj1" path="res://components/client_interface.gd" id="2_e2o6t"] + +[sub_resource type="SystemFont" id="SystemFont_80nbo"] +font_names = PackedStringArray("IBM Plex Mono") +font_weight = 500 + +[node name="Game" type="Node"] + +[node name="WebClientTCP" type="Node" parent="."] +script = ExtResource("1_feb5d") + +[node name="ClientInterface" type="Control" parent="." node_paths=PackedStringArray("client", "connect_button", "create_lobby_button", "join_lobby_button", "leave_lobby_button", "name_input", "lobby_code_input")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("2_e2o6t") +client = NodePath("../WebClientTCP") +connect_button = NodePath("HBoxContainer/VBoxContainer/ConnectButton") +create_lobby_button = NodePath("HBoxContainer/VBoxContainer/CreateLobbyButton") +join_lobby_button = NodePath("HBoxContainer/VBoxContainer/JoinLobbyButton") +leave_lobby_button = NodePath("HBoxContainer/VBoxContainer/LeaveLobbyButton") +name_input = NodePath("HBoxContainer/VBoxContainer/NameInput") +lobby_code_input = NodePath("HBoxContainer/VBoxContainer/LobbyCodeInput") + +[node name="ColorRect" type="ColorRect" parent="ClientInterface"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.34, 0.33116, 0.2992, 1) + +[node name="HBoxContainer" type="HBoxContainer" parent="ClientInterface"] +layout_mode = 0 +offset_right = 800.0 +offset_bottom = 1080.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="ClientInterface/HBoxContainer"] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 + +[node name="ConnectButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +text = "Connect to Server" + +[node name="NameInput" type="LineEdit" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Your Name" +editable = false + +[node name="CreateLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +disabled = true +text = "Create Lobby" + +[node name="LobbyCodeInput" type="LineEdit" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Lobby Code" +editable = false + +[node name="JoinLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +disabled = true +text = "Join Lobby" + +[node name="LeaveLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +disabled = true +text = "Disconnect" + +[node name="Output" type="Label" parent="ClientInterface/HBoxContainer"] +custom_minimum_size = Vector2(64, 1) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 1 +theme_override_fonts/font = SubResource("SystemFont_80nbo") +autowrap_mode = 2 + +[connection signal="connected_to_server" from="WebClientTCP" to="ClientInterface" method="_on_web_client_tcp_connected_to_server"] +[connection signal="connection_error_occurred" from="WebClientTCP" to="ClientInterface" method="_on_web_client_tcp_connection_error_occurred"] +[connection signal="disconnected_from_server" from="WebClientTCP" to="ClientInterface" method="_on_web_client_tcp_disconnected_from_server"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/ConnectButton" to="ClientInterface" method="_on_connect_button_pressed"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/CreateLobbyButton" to="ClientInterface" method="_on_create_lobby_button_pressed"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/JoinLobbyButton" to="ClientInterface" method="_on_join_lobby_button_pressed"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/LeaveLobbyButton" to="ClientInterface" method="_on_disconnect_lobby_button_pressed"] diff --git a/globals/tcp_messages.gd b/globals/tcp_messages.gd new file mode 100644 index 0000000..ead319c --- /dev/null +++ b/globals/tcp_messages.gd @@ -0,0 +1,35 @@ +extends Node + +func create_lobby( + lobby_id: String, + client_id: String, + client_name: String, +) -> String: + return JSON.stringify({ + "response": "create_lobby", + "lobby_id": lobby_id, + "client_id": client_id, + "client_name": client_name, + }) + +func join_lobby( + lobby_id: String, + client_id: String, + client_name: String, +) -> String: + return JSON.stringify({ + "response": "join_lobby", + "lobby_id": lobby_id, + "client_id": client_id, + "client_name": client_name, + }) + +func leave_lobby( + lobby_id: String, + client_id: String, +) -> String: + return JSON.stringify({ + "response": "leave_lobby", + "lobby_id": lobby_id, + "client_id": client_id, + }) diff --git a/globals/tcp_messages.gd.uid b/globals/tcp_messages.gd.uid new file mode 100644 index 0000000..9c90002 --- /dev/null +++ b/globals/tcp_messages.gd.uid @@ -0,0 +1 @@ +uid://23svlnpyxlw2 diff --git a/project.godot b/project.godot index dd1a48c..eb9c1d4 100644 --- a/project.godot +++ b/project.godot @@ -17,7 +17,7 @@ config/icon="res://icon.svg" [autoload] -RPCHandler="*res://rpc_handler.tscn" +TCPMessages="*res://globals/tcp_messages.gd" [display] diff --git a/server_tcp.py b/server_tcp.py new file mode 100644 index 0000000..53d4482 --- /dev/null +++ b/server_tcp.py @@ -0,0 +1,168 @@ +import json +import socket +import sys +import threading + +# Establishes connections to TCP clients. +def listen() -> None: + lobby_manager = LobbyManager() + threads = [] + while True: + try: + connection, address = tcp_socket.accept() + new_thread = threading.Thread(target=start_listen_client, args=(connection, address, lobby_manager)) + new_thread.start() + threads.append(new_thread) + except socket.timeout: + pass + except KeyboardInterrupt: + tcp_socket.close() + +def start_listen_client(connection, address, lobby_manager): + ClientConnection(connection, address, lobby_manager) + +class Lobby: + + def __init__(self, lobby_id, host_client_id): + self.lobby_id = lobby_id + self.host_client_id = host_client_id + self.connected_clients = {} + +class Client: + + def __init__(self, client_id, client_name, address): + self.client_id = client_id + self.client_name = client_name + self.address = address + +class LobbyManager: + + def __init__(self): + self.active_lobbies = {} + + def get_lobby(self, lobby_id) -> Lobby: + return self.active_lobbies[lobby_id] + + def create_lobby(self, lobby_id, host_client_id) -> None: + self.active_lobbies[lobby_id] = Lobby(lobby_id, host_client_id) + print(f"Created lobby {lobby_id}") + + def delete_lobby(self, lobby_id) -> None: + try: + del self.active_lobbies[lobby_id] + print(f"Deleted lobby {lobby_id}") + except KeyError: + print(f"Lobby with ID {lobby_id} not found, could not be deleted") + + def join_lobby(self, lobby_id, client_id, client_name, address) -> None: + self.active_lobbies[lobby_id].connected_clients[client_id] = Client(client_id, client_name, address) + print(f"Client {client_id} joined lobby {lobby_id}") + + def leave_lobby(self, lobby_id, client_id) -> None: + try: + del self.active_lobbies[lobby_id].connected_clients[client_id] + print(f"Deleted client {client_id} from lobby {lobby_id}") + except KeyError: + print(f"Client with ID {client_id} in lobby {lobby_id} not found, could not be deleted") + + def get_lobby_host_client_id(self, lobby_id) -> str: + return self.active_lobbies[lobby_id].host_client_id + +class ClientConnection: + + def __init__(self, connection, address, lobby_manager): + self.connection = connection + self.address = address + self.lobby_manager = lobby_manager + self.listen_client() + + # Listens to a connected TCP client. + def listen_client(self) -> None: + try: + print(f"connected to {self.address[0]}:{self.address[1]}") + + received_data = "" + while True: + data = self.connection.recv(1) + received_data += data.decode("utf-8") + print(received_data) + if received_data[-1] == "}": + self.process_input(received_data) + received_data = "" + + if not data: + break + except IndexError: + print("Client force-disconnected") + finally: + self.connection.close() + print(f"Closing connection to {self.address[0]}:{self.address[1]}") + + def process_input(self, data: str) -> None: + data_object = json.loads(data) + + if data_object["response"] == "create_lobby": + print(data_object["client_id"] + " is creating lobby " + data_object["lobby_id"]) + self.lobby_manager.create_lobby( + data_object["lobby_id"], + data_object["client_id"], + ) + self.lobby_manager.join_lobby( + data_object["lobby_id"], + data_object["client_id"], + data_object["client_name"], + self.address, + ) + self.send_message(json.dumps({ + "response": "create_lobby", + "status": "ok", + })) + + elif data_object["response"] == "join_lobby": + print("joining lobby") + self.lobby_manager.join_lobby( + data_object["lobby_id"], + data_object["client_id"], + data_object["client_name"], + self.address, + ) + self.send_message(json.dumps({ + "response": "join_lobby", + "status": "ok", + })) + + # send message to all connected clients + + elif data_object["response"] == "leave_lobby": + print("leaving lobby") + lobby_id = data_object["lobby_id"] + host_client_id = self.lobby_manager.get_lobby_host_client_id(lobby_id) + + if host_client_id == data_object["client_id"]: + # client is host; close lobby + lobby_clients = self.lobby_manager.get_lobby(lobby_id).connected_clients.copy() + for client_id in lobby_clients: + self.lobby_manager.leave_lobby(lobby_id, client_id) + self.lobby_manager.delete_lobby(lobby_id) + else: + # client is not host; client leaves only + self.lobby_manager.leave_lobby(lobby_id, data_object["client_id"]) + + def send_message(self, message: str) -> None: + self.connection.sendall(str.encode(message)) + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: ./server.py PORT") + sys.exit(1) + + port = int(sys.argv[1]) + tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_address = ('localhost', port) + tcp_socket.bind(server_address) + tcp_socket.listen(1) + tcp_socket.settimeout(0.5) + + print('Listening on *:%d' % (port)) + + listen() \ No newline at end of file