How do I send data with StreamPeerTCP?
Asked Answered
O

6

0

I'm using Godot v4.0.1 and have a code like this somewhere:

_stream.connect_to_host("127.0.0.1", 2357)
var message = PackedByteArray([0, 1, 112, 108, 97, 121, 101, 114, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 101, 105, 105, 103])
print("Putting data:", message, "(", len(message), ")")
# prints: Putting data:[0, 1, 112, 108, 97, 121, 101, 114, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 101, 105, 105, 103](22)
_stream.set_no_delay(true)
_stream.put_data(message)

On the other side (the server) I have some Python code that expects code:

data = remote.rfile.read(22)  # where remote is socketserver.StreamRequestHandler

The problem: It seems that the GODOT stream does not send the data, because the rfile keeps reading forever or until I destroy the connection.

On the other hand, other python programs can interact with my server properly:

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 2357))
auth_buffer = bytes([0, 1, 112, 108, 97, 121, 101, 114, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 101, 105, 105, 103])
client.send(auth_buffer)

This one sends the message properly, which is handled by the server.

What am I missing here?

Outdate answered 25/5, 2023 at 4:1 Comment(0)
R
0

My guess is that you're trying to send over the connection before it's been fully established, it may take a few frames after the initial connect_to_host call to get to "connected" status. Try checking get_status before sending data to verify that you're connected (and for that matter maybe check the return value of connect_to_host as well). You also might need to call poll each frame (or at least each interaction) to update the connection state.

Here's an article on setting up a StreamPeerTCP client, it was made for Godot 3 so the code won't work as is in a 4.x project but the principles should still hold:

https://www.bytesnsprites.com/posts/2021/creating-a-tcp-client-in-godot/

Railroad answered 25/5, 2023 at 6:42 Comment(0)
O
0

Railroad I just checked and, after deferring the .put_data as you recommended (I took the .put_data out and deferred it into the _process callback by status check and some extra checks), I notice that the whole problem seems that the socket NEVER leaves the STATUS_CONNECTING (1) status. Why does that happen? I don't have that problem with sockets in other languages.

In the meantime, I have to clarify: The server I created in Python DOES detect the new incoming connection, which means that the connection IS already established in the server side (even when attempting with the Godot client). It is only that, client-wise, the Godot socket (StreamPeerTCP) remains in the STATUS_CONNECTING (1) status, even after being successfully detected in the server side.

Outdate answered 25/5, 2023 at 13:24 Comment(0)
R
0

Outdate Can you post your _process function?

Railroad answered 25/5, 2023 at 17:45 Comment(0)
O
0

Railroad I'll do something better! I've isolated external examples -not tied to my game at all- where the same issue can be reproduced.

This is an ECHO SERVER made in Python3. It is zero-dependency as long as you use a modern Python3 version (e.g. I use 3.9 here):

#!/usr/bin/env python
import socketserver


class EchoHandler(socketserver.StreamRequestHandler):

    def setup(self) -> None:
        super(EchoHandler, self).setup()
        print("Client connection starting")

    def handle(self) -> None:
        while True:
            # First, get the message length.
            b = self.rfile.read(1)
            if len(b) == 0:
                break

            # Then, get the message.
            size = b[0]
            buffer = self.rfile.read(size)
            if len(buffer) < size:
                break

            # Finally, send everything again
            self.wfile.write(bytes([size]) + bytes(buffer))

    def finish(self) -> None:
        print("Client connection stopping")
        super(EchoHandler, self).finish()


if __name__ == "__main__":
    with socketserver.ThreadingTCPServer(("0.0.0.0", 2360), EchoHandler, True) as server:
        server.serve_forever()

Then, this is the Python ECHO client:

#!/usr/bin/env python
import socket
import time

if __name__ == "__main__":
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(("127.0.0.1", 2360))

    for index in range(5):
        value = f"Hello world #{index}".encode('utf-8')
        length = len(value)
        buffer = bytes([length]) + value
        client.send(buffer)
        time.sleep(0.25)
        response = client.recv(length + 1)
        print(f"Received: {response[1:].decode('utf-8')}")
        time.sleep(0.25)

    client.close()

And finally, a dumb Godot v4.0.1 program. To run this program, do like this:

  • Create an empty Godot project.
  • Put a dumb Control on it.
  • Attach to that control the following script.
  • Run it in desktop, under the same conditions you'd run the Python script.

So the Godot script is this:

extends Control


var _timer = 0
var _count = 0
var _stream = null


# Called when the node enters the scene tree for the first time.
func _ready():
	_stream = StreamPeerTCP.new()
	_stream.set_no_delay(true)
	print("Connection attempt: ", _stream.connect_to_host("127.0.0.1", 2360))
	# I also tried _stream.set_no_delay(true) here instead of above.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	var status = _stream.get_status()
	if status != StreamPeerTCP.STATUS_CONNECTED:
		print("Socket not connected yet. Status is: ", status)

	if _timer > 3:
		_timer -= 3
		if _count > 0:
			_stream.put_data([8, 1, 2, 3, 4, 5, 6, 7, _count])
			_count -= 1
	_timer += delta

	if _stream.get_available_bytes():
		var response = _stream.get_data(_stream.get_available_bytes())
		print("Received: ", response)


func _input(event):	
	if event is InputEventKey:
		if event.keycode == 32:
			print("Resetting count to 5")
			_count = 5

All the logs like this:

Socket not connected yet. Status is: 1
Socket not connected yet. Status is: 1
Socket not connected yet. Status is: 1
Socket not connected yet. Status is: 1
Socket not connected yet. Status is: 1
Socket not connected yet. Status is: 1
...

Where 1 stands for "StreamPeerTCP.STATUS_CONNECTING"

Outdate answered 25/5, 2023 at 20:2 Comment(0)
O
0

Outdate Nevermind 😃, sorry. I just overlooked part of your answer.

I added _stream.poll() inside _process() in my Godot sample echo client, and it worked. I'll try the same in my original script.

Outdate answered 25/5, 2023 at 21:0 Comment(0)
H
0

Outdate Could you post a test script that worked for you?

Henn answered 7/11, 2023 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.