How to use the TCPserver & StreamPeerTCP classes to send packets back and forth between the server and client?

Godot Version

4.6.2

Background

I’m currently working on my netcode rewrite for Monkanics. The next step in my plan is to create a STUN (Session Traversal Utilities for NAT) signal server using my now working relay server.

However, while I did get the UDPserver & PacketPeerUDP working, I did not account for TCP connections, their utility, and how to use TCPserver combined with StreamPeerTCP

I can use TCP and it’s built-in reliability for a foundational, stable connection and UDP for game logic transfer as a backup for the STUN server.

Question

How do I send packets back and forth using TCPserver and StreamPeerTCP?

For reference, here is the working UDP packet transfer code:


Server:

var Relay_Router_UDP : UDPServer
const MAX_PENDING_CONNECTIONS : int = 60
func _process(_delta: float) -> void:
	
	poll_udp_server()
func create_relay_servers() -> void:
	
	Relay_Router_UDP = UDPServer.new()
	
	Relay_Router_UDP.listen(RelayInfo.RELAY_ROUTER_PORT, RelayInfo.LOCALHOST_IPV4)
	
	Relay_Router_UDP.max_pending_connections = MAX_PENDING_CONNECTIONS
func poll_udp_server() -> void:
	
	Relay_Router_UDP.poll()
	
	if Relay_Router_UDP.is_connection_available():
		
		var Connecting_Client := Relay_Router_UDP.take_connection()
		
		var Packet : Variant = Connecting_Client.get_packet()
		
		print("Accepted peer: %s:%s" % [Connecting_Client.get_packet_ip(), Connecting_Client.get_packet_port()])
		
		print("Received data: %s" % [Packet.get_string_from_utf8()])
		
		# Reply so it knows we received the message.
		send_udp_packet_to_relay_client(Connecting_Client, Packet)

func send_udp_packet_to_relay_client(Client:Variant, Packet:Variant) -> void:
	
	Client.put_packet(Packet)

Client:

var Relay_Client_UDP : PacketPeerUDP
func _process(_delta: float) -> void:
	
	send_udp_packet_to_relay_server()
	
	receive_udp_packet_from_relay_server()
func initialize_relay_client() -> void:
	
	Relay_Client_UDP = PacketPeerUDP.new()
	
	Relay_Client_UDP.bind(RelayInfo.RELAY_ROUTER_PORT)
	
	Relay_Client_UDP.connect_to_host(RelayInfo.LOCALHOST_IPV4, RelayInfo.RELAY_ROUTER_PORT)	
func send_udp_packet_to_relay_server() -> void:
	
	Relay_Client_UDP.put_packet("Message Sent".to_utf8_buffer())

func receive_udp_packet_from_relay_server() -> void:
	
	if Relay_Client_UDP.get_available_packet_count() > 0:
		var array_bytes : Variant = Relay_Client_UDP.get_packet()
		var packet_string : Variant = array_bytes.get_string_from_ascii()
		print("Received message: ", packet_string)

Result:

(Localhost was used as an example, but this exact code works for the relay server’s ip, which is called the relay router for flavor)


How do I achieve the same thing with the TCP classes?

BTW, TCPserver doesn’t have a connection example in the documentation like UDPserver does. Which I find really odd.

Any help would be appreciated!

Also, I feel like these 2 would have a better idea of what I’m on about.
@pennyloafers @gertkeno

You can use TCPServer pretty much the same but without .poll

Simple TCP server example:

Replace contents in use_stream() with your desired behavior.

extends Node;

const DEFAULT_PORT: int = 19388;
var server: TCPServer;


func use_stream(stream: StreamPeerTCP):
    # Your Code Here
    pass;

func _ready():
    server = TCPServer.new();
    
    server.listen(DEFAULT_PORT);
func _process(_dt: float):
    while server.is_connection_available():
        var stream: StreamPeerTCP = server.take_connection();


        use_stream(stream);

Thanks for the help guys.

While I didn’t get the TCP classes working, after more research, I’ve realized that I don’t need the TCP protocol at all to make Monkanics.

Besides the fact that nobody has documented or even used the raw TCP classes (Sorry future Google searchers), UDP can be built with a custom reliability layer. And it’s surprisingly simple too.

I just need to implement a packet acknowledgement function and a packet sequencer.

It sounds complex, but the former is just another packet sender function with put_packet() and the latter is literally just an integer that goes +1 with every packet & a code block to sequence the packets when using poll().

Also, ENet uses only UDP, but with those tags that determine if the packet needs acknowledgement (reliable), ordered (unreliable_ordered), or nothing at all (unreliable) as it’s own custom reliability layer.

Also also, when people say UDP is “unreliable”, it only means that there’s no guarantees since it’s fire-and-forget. But most modern networks (at least in 1st-world countries like the US) have a 99.99% packet delivery success rate.


Again, sorry to the one guy in the future who wants to use the TCP classes and stumbled their way here hoping for an answer.

Here’s a like as compensation:

image

Maybe for wired networks and datacenter-to-datacenter transactions but anybody on WiFi or mobile networks will have a much worse packet drop rate. That last mark from router to most consumer PCs is the worst.

Noted.