跳至主要內容

tcp,udp,websocket in Godot

leonhardt...大约 3 分钟Bloggodot

tcp,udp,websocket in Godot

UDPServer

用来接受来自其他程序的UDP数据包。是基于PacketPeerUDP封装的类。

基础使用代码

extends Node

const UDP_IP = "127.0.0.1"
const UDP_PORT = 4243

var server := UDPServer.new()


func _ready():
    set_process(false)
    # 开启监听端口
    server.listen(UDP_PORT)
    set_process(true)


func _notification(what):
    if what == NOTIFICATION_WM_CLOSE_REQUEST:
        server.stop()
	    set_process(false)
        get_tree().quit()


func _process(_delta):
    # 接受数据包
    server.poll()
    if server.is_connection_available():
        var peer : PacketPeerUDP = server.take_connection()
        var packet : PackedByteArray = peer.get_packet()

        print("Accepted peer: %s:%s" % [peer.get_packet_ip(), peer.get_packet_port()])
        print("Received data: %s" % [packet.get_string_from_utf8()])

PacketPeerUDP使用示例

extends Node

var udp := PacketPeerUDP.new()
var connected = false

func _ready():
    udp.connect_to_host("127.0.0.1", 4242) # 确定目标

func _process(delta):
    if !connected:
        # Try to contact server
        udp.put_packet("The answer is... 42!".to_utf8_buffer()) # 发包
    if udp.get_available_packet_count() > 0:
        print("Connected: %s" % udp.get_packet().get_string_from_utf8()) # 收包
        connected = true

python 客户端代码

import socket
import json
import time

opened_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
UDP_IP = "127.0.0.1"
UDP_PORT = 4243
lastChat = []

def main():
    first = True
    count = 0
    while True:
        if first: # 第一次可以将进程id发过去
            send_chat(["PIDs", "Greetings! 9527"])
            first = False
        
        count += 1
        send_chat(["CHAT", f"Hello from python {count}"])
        time.sleep(3) # Reduce CPU Usage

def send_chat(chat): # 发包
    byte_message = bytes(json.dumps(chat), "utf-8")
    opened_socket.sendto(byte_message, (UDP_IP, UDP_PORT))


if __name__ == "__main__":
    main()

TCP

StreamPeerTCP

extends Node

var tcp_stream := StreamPeerTCP.new()
var status := 0

func _ready():
    status = tcp_stream.get_status()
    print(status)
    # tcp_stream.bind(8083)
    connect_to_host("127.0.0.1", 50007)

func _process(_delta: float):
    tcp_stream.poll() # 需要手动拉取socket否则status会一直是STATUS_CONNECTING
    var new_status: int = tcp_stream.get_status()
    if new_status != status:
        status = new_status
        match status:
            tcp_stream.STATUS_NONE:
                print("Disconnected from host.")
            tcp_stream.STATUS_CONNECTING:
                print("Connecting to host.")
            tcp_stream.STATUS_CONNECTED:
                print("Connected to host.")
            tcp_stream.STATUS_ERROR:
                print("Error with socket stream.")

    if status == tcp_stream.STATUS_CONNECTED:
        var available_bytes: int = tcp_stream.get_available_bytes()
        if available_bytes > 0:
            print("available bytes: ", available_bytes)
            var data: Array = tcp_stream.get_partial_data(available_bytes)
            # Check for read error.
            if data[0] != OK:
                print("Error getting data from stream: ", data[0])
            else:
                print("Data", data[1].get_string_from_utf8())

func connect_to_host(host: String, port: int):
    print("Connecting to %s:%d" % [host, port])
    # Reset status so we can tell if it changes to error again.
    status = tcp_stream.STATUS_NONE
    if tcp_stream.connect_to_host(host, port) != OK:
        print("Error connecting to host.")

func send(data: PackedByteArray) -> bool:
    if status != tcp_stream.STATUS_CONNECTED:
        print("Error: Stream is not currently connected.")
        return false
    var error: int = tcp_stream.put_data(data)
    if error != OK:
        print("Error writing to stream: ", error)
        return false
    return true

python 客户端代码

import socket
import time


# address = ('127.0.0.1', 8083)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 50007))

#最大连接数
s.listen(5)

#客户处理线程
def handle_client(client_socket: socket.socket):

    # request = client_socket.recv(1024)
    # print("[*] Received: %s" % request)
    #向客户端返回数据
    try:
        client_socket.send("hi~".encode())
    except ConnectionAbortedError:
        print(f"Disconnect with {client_socket.getpeername()}")
        return

    time.sleep(0.5)


while True:
    #等待客户连接,连接成功后,将socket对象保存到client,将细节数据等保存到addr
    client, addr = s.accept()
    print("[*] Acception connection from %s:%d" % (addr[0],addr[1]))

    while True:
        # client_handler = threading.Thread(target=handle_client,args=(client,))
        # client_handler.start()
        handle_client(client)

Websocket

WebsocketPeer

extends Node

var socket = WebSocketPeer.new()

@onready var send_button: Button = $Button

func _ready():
    socket.connect_to_url("ws://localhost:5000")
    send_button.pressed.connect(on_pressed)

func _process(_delta):
    socket.poll()
    var state = socket.get_ready_state()
    if state == WebSocketPeer.STATE_OPEN:
        while socket.get_available_packet_count():
            var pkt = socket.get_packet()
            print("Packet: ", pkt)
            $RichTextLabel.text = "Packet: %s" % pkt.get_string_from_utf8()
    elif state == WebSocketPeer.STATE_CLOSING:
        # Keep polling to achieve proper close.
        pass
    elif state == WebSocketPeer.STATE_CLOSED:
        var code = socket.get_close_code()
        var reason = socket.get_close_reason()
        print("WebSocket closed with code: %d, reason %s. Clean: %s" % [code, reason, code != -1])
        set_process(false) # Stop processing.

func on_pressed():
    print($LineEdit.text)
    socket.send_text($LineEdit.text)

python 客户端代码

import asyncio
import websockets # pip install websocket

async def server(ws, path):
    async for msg in ws:
        # msg = msg.decode("utf-8")
        print(f"Msg from client: {msg}")
        data = f"Got our msg: {msg}".encode("utf-8")
        print(data)
        await ws.send(data)

start_server = websockets.serve(server, "localhost", 5000)
print("Server started")
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

意外收获

GDScript stores values in Variant, which is what allows GDScript to be dynamically typed. Variant has a 16 byte payload, with 4 bytes of information about what that payload is, so it is 20 bytes, but is padded to 24 bytes on a modern 64-bit system. So, a GDScript int is 3 times the size of int64. This may change with typed instructions in Godot 4+.

一个var占24bytes。如果声明了类型则只占类型所需的bytes。