Communicating Between Godot and Python Over TCP

Godot Version

4.2

Question

I’m trying to implement some simple back and forth communication between my game and a separate process running on my server. I’ve been trying to use StreamPeerTCP but I’m running into a strange issue. One-way communication works without issue, with the following code, I get the expected Received '12345' on my server.

# Godot snippet
func create_session() -> void:
	var peer = StreamPeerTCP.new()

	peer.connect_to_host("localhost", 6789)
	while peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
		peer.poll()

	peer.put_data("12345".to_ascii_buffer())

	# while peer.get_available_bytes() < 5:
	#	peer.poll()

	# var data := peer.get_data(5)

	# print(data)
# Python TCP server
async def main():
    server = await asyncio.start_server(handle_request, 'localhost', 6789)

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()


async def handle_request(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    data = await reader.read()
    message = data.decode("ascii")

    print(f"Received '{message}'") 
    # writer.write(b"54321")
    # await writer.drain()

    # writer.close()

However, when I attempt to send a response back (i.e. uncomment the commented-out lines), the Godot process freezes up, continuously polling and never receiving any data. What’s even more strange is that the Python process never gets the sent data until I forcibly close the Godot process. I’ve tried setting the no-delay mode to true, but that didn’t help.

What am I doing wrong here? Is there a better way to handle this use case? I’m truly at a loss.

Looks like a TCP-level issue - it buffers small messages into larger buffers so it doesn’t have to actually transmit data as often. I think if you were to send data at regular intervals this could soften somewhat to receiving data but with a delay.

Did you turn off delay on both ends?

I added peer.set_no_delay(true) to the Godot side and server.sockets[0].setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) to the Python side (which seems to be the default for TCP sockets anyway), and the behavior is the same.

From the behaviour you described it sounds like peer.put_data won’t send the data if the main thread is blocked.
Have you already tried polling the peer every frame instead of just blocking the whole main thread?

1 Like

I tried putting the read inside of _process:

func _process(_delta: float) -> void:
	if not tcp_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED:
		return

	tcp_peer.poll()
	if tcp_peer.get_available_bytes() < 5:
		#print("Still waiting: ", tcp_peer.get_available_bytes())
		return

	var data := tcp_peer.get_data(5)
	create_session_callback(data)

The main thread no longer blocks, but it still never receives data, and the Python process only gets the sent data when I exit Godot.

In your python server code, I changed

data = await reader.read()

into

data = await reader.readexactly(5)

and it worked. So it seems reader.read() got stuck forever waiting for more data - according to the docs (Streams — Python 3.12.1 documentation) it will wait for EOF.

1 Like

I guess in the send-only case the StreamPeerTCP would send EOF when going out of scope, and that’s why it was working before. Nice catch!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.