Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

UPNP

繼承: RefCounted < Object

通用隨插即用(UPnP)功能,用於網路裝置的發現、查詢及埠對應。

說明

這個類可以用來在本機網路中發現相容的 UPNPDevice(UPNP 裝置),並對它們執行命令,比如管理連接埠映射(用於連接埠轉發 / NAT 穿透)以及查詢本機和遠端網路的 IP 位址。需要注意的是,這個類中的方法都是同步的,並且會阻塞(卡住)呼叫它的執行緒。

要轉發一個特定的連接埠(這裡是 7777,注意 discover() 和 add_port_mapping() 都有可能回傳錯誤,程式碼中應該對這些錯誤進行檢查):

var upnp = UPNP.new()
upnp.discover()
upnp.add_port_mapping(7777)

To close a specific port (e.g. after you have finished using it):

upnp.delete_port_mapping(port)

Note: UPnP discovery blocks the current thread. To perform discovery without blocking the main thread, use Threads like this:

# 當 UPnP 連接埠映射設定完成時發出該訊號(無論成功還是失敗)。
signal upnp_completed(error)

# 請將其替換為你自己的伺服器連接埠號(範圍在 1024 到 65535 之間)。
const SERVER_PORT = 3928
var thread = null

func _upnp_setup(server_port):
    # UPNP queries take some time.
    var upnp = UPNP.new()
    var err = upnp.discover()

    if err != UPNP.UPNP_RESULT_SUCCESS:
        push_error(str(err))
        upnp_completed.emit(err)
        return

    if upnp.get_gateway() and upnp.get_gateway().is_valid_gateway():
        upnp.add_port_mapping(server_port, server_port, ProjectSettings.get_setting("application/config/name"), "UDP")
        upnp.add_port_mapping(server_port, server_port, ProjectSettings.get_setting("application/config/name"), "TCP")
        upnp_completed.emit(UPNP.UPNP_RESULT_SUCCESS)

func _ready():
    thread = Thread.new()
    thread.start(_upnp_setup.bind(SERVER_PORT))

func _exit_tree():
    # 在此處等待執行緒執行完畢,以便線上程仍在執行時妥善處理遊戲退出。
    thread.wait_to_finish()

術語說明: 在 UPnP 網路的語境中,“網關”(或稱“互連網網關裝置”,簡稱 IGD)指的是那些允許本機網路中的電腦存取互連網(即“廣域網”,WAN)的網路裝置。這些網關通常也被稱為“路由器”。

常見陷阱:

正如上文所述,這些呼叫都是阻塞式的,因此不應該在主執行緒上執行,尤其是考慮到它們可能會一次性卡住好幾秒鐘。請務必使用多執行緒!

網路環境是物理層面的,且充滿了各種不確定性。封包在傳輸中可能會丟失或被過濾,IP 位址、空閒連接埠以及已分配的映射關係隨時可能發生變化,裝置也可能隨時加入或離開網路。請務必考慮到這些因素,在檢查和處理錯誤時要格外仔細;如果可能的話,請優雅地應對這些問題:提供清晰的錯誤提示介面(UI),並設定超時與重試機制。

連接埠映射隨時可能發生變化(甚至被移除),同樣地,網關的遠端/外部 IP 位址也可能發生改變。你應該考慮定期重新查詢外部 IP,並嘗試更新/刷新連接埠映射(例如,每隔 5 分鐘以及在發生網路故障時進行更新)。

並非所有的裝置都支援 UPnP,而且有些使用者會主動停用 UPnP 功能。你需要對此做好應對準備(例如,在文件中說明並讓使用者手動轉發連接埠,或者加入其他替代的 NAT 穿透方法,比如使用中繼/鏡像伺服器、NAT 打洞、STUN/TURN 等)。

請考慮發生映射衝突時會發生甚麼。也許同一個網路下的多個使用者想要同時玩你的遊戲,或者也許有其他應用程式佔用了同一個連接埠。請將連接埠設定為可設定的,並且最好能自動選擇一個連接埠(在失敗時嘗試使用不同的連接埠進行重試)。

拓展閱讀: 如果你想瞭解更多關於 UPnP(特別是互連網網關裝置 IGD 和連接埠控制協議 PCP)的資訊,維基百科 是一個不錯的入門站點;相關規範可以在 開放連接基金會(Open Connectivity Foundation) 找到;而 Godot 的實作則是基於 MiniUPnP 用戶端。

屬性

bool

discover_ipv6

false

int

discover_local_port

0

String

discover_multicast_if

""

方法

void

add_device(device: UPNPDevice)

int

add_port_mapping(port: int, port_internal: int = 0, desc: String = "", proto: String = "UDP", duration: int = 0) const

void

clear_devices()

int

delete_port_mapping(port: int, proto: String = "UDP") const

int

discover(timeout: int = 2000, ttl: int = 2, device_filter: String = "InternetGatewayDevice")

UPNPDevice

get_device(index: int) const

int

get_device_count() const

UPNPDevice

get_gateway() const

String

query_external_address() const

void

remove_device(index: int)

void

set_device(index: int, device: UPNPDevice)


列舉

enum UPNPResult: 🔗

UPNPResult UPNP_RESULT_SUCCESS = 0

UPNP 命令或發現成功。

UPNPResult UPNP_RESULT_NOT_AUTHORIZED = 1

未授權在 UPNPDevice 上使用該命令。當使用者在其路由器上停用 UPNP 時,可能會被返回。

UPNPResult UPNP_RESULT_PORT_MAPPING_NOT_FOUND = 2

在給定的 UPNPDevice 上沒有找到給定埠、協定組合的埠對應。

UPNPResult UPNP_RESULT_INCONSISTENT_PARAMETERS = 3

參數不一致。

UPNPResult UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY = 4

陣列中沒有此條目。可能在 UPNPDevice 上沒有找到給定的連接埠、協議組合時回傳。

UPNPResult UPNP_RESULT_ACTION_FAILED = 5

操作失敗。

UPNPResult UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED = 6

UPNPDevice 不允許源 IP 位址的萬用字元值。

UPNPResult UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED = 7

UPNPDevice 不允許外部埠的萬用字元值。

UPNPResult UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED = 8

UPNPDevice 不允許內部埠的萬用字元值。

UPNPResult UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD = 9

遠端主機值必須是萬用字元。

UPNPResult UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD = 10

外部埠值必須是萬用字元。

UPNPResult UPNP_RESULT_NO_PORT_MAPS_AVAILABLE = 11

沒有可用的埠對應。如果埠對應功能不可用,也可能被返回。

UPNPResult UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM = 12

與其他機制衝突。如果一個埠對應與現有的衝突,可能會被返回,而不是UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING。

UPNPResult UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING = 13

與現有的埠對應相衝突。

UPNPResult UPNP_RESULT_SAME_PORT_VALUES_REQUIRED = 14

外部和內部埠值必須相同。

UPNPResult UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED = 15

只支援永久租用。在新增埠對應時,不要使用 duration 參數。

UPNPResult UPNP_RESULT_INVALID_GATEWAY = 16

無效閘道。

UPNPResult UPNP_RESULT_INVALID_PORT = 17

無效埠。

UPNPResult UPNP_RESULT_INVALID_PROTOCOL = 18

無效協議。

UPNPResult UPNP_RESULT_INVALID_DURATION = 19

無效持續時間。

UPNPResult UPNP_RESULT_INVALID_ARGS = 20

無效參數。

UPNPResult UPNP_RESULT_INVALID_RESPONSE = 21

無效回應。

UPNPResult UPNP_RESULT_INVALID_PARAM = 22

無效參數。

UPNPResult UPNP_RESULT_HTTP_ERROR = 23

HTTP 錯誤。

UPNPResult UPNP_RESULT_SOCKET_ERROR = 24

通訊端錯誤。

UPNPResult UPNP_RESULT_MEM_ALLOC_ERROR = 25

分配記憶體時出錯。

UPNPResult UPNP_RESULT_NO_GATEWAY = 26

沒有可用的閘道。你可能需要先呼叫 discover() ,否則發現沒有偵測到任何有效的 IGD(InternetGatewayDevices)。

UPNPResult UPNP_RESULT_NO_DEVICES = 27

沒有可用的裝置。你可能需要先呼叫 discover(),或者發現沒有偵測到任何有效的 UPNPDevice。

UPNPResult UPNP_RESULT_UNKNOWN_ERROR = 28

未知錯誤。


屬性說明

bool discover_ipv6 = false 🔗

  • void set_discover_ipv6(value: bool)

  • bool is_discover_ipv6()

如果為 true,則 IPv6 用於 UPNPDevice 發現。


int discover_local_port = 0 🔗

  • void set_discover_local_port(value: int)

  • int get_discover_local_port()

如果為 0,系統會自動選擇用於發現的本地埠。如果為 1,將從源埠 1900 進行發現(與目的埠相同)。否則,將使用該值作為埠。


String discover_multicast_if = "" 🔗

  • void set_discover_multicast_if(value: String)

  • String get_discover_multicast_if()

用於發現的多播介面。如果為空,則使用預設的多播介面。


方法說明

void add_device(device: UPNPDevice) 🔗

將給定的 UPNPDevice 新增到已發現裝置的列表中。


int add_port_mapping(port: int, port_internal: int = 0, desc: String = "", proto: String = "UDP", duration: int = 0) const 🔗

新增對應,針對給定的協議 proto("TCP" 或 "UDP",預設為 UDP),將預設閘道器(見 get_gateway())上的外部埠 port(在 1 到 65535 之間,不過推薦使用 1024 以上的埠)對應到本機上的內部埠 port_internal。如果該閘道上已經存在給定的埠與協議的組合,這個方法會嘗試進行覆蓋。如果不希望如此,你可以使用 get_gateway() 手動取得閘道,取得到後呼叫其 add_port_mapping() 方法。請注意,使用 UPnP 轉發公認埠(1024 以下)在有些裝置上可能會失敗。

如果埠的對應已存在,有些閘道裝置可能會對其進行更新,有些則會因為衝突而拒絕這個命令,尤其當現有埠對應不是由 UPnP 建立的,或者指向的是別的網路位址(或裝置)的時候。

如果 port_internal 為 0(預設),表示內外部埠相同(使用 port 的值)。

描述(desc)會顯示在一些路由器的管理介面上,可以用來識別新增對應的程序。

對應的租賃時長 duration 可以通過指定秒數來限定。預設的 0 表示沒有時長,即永久租賃,有些裝置只支援這種永久租賃。請注意,無論是否永久都只是一種請求,閘道仍然可以隨時移除對應(通常發生在重新啟動閘道後外部 IP 地址發生變化時,也有些型號會在對應不再活動,即若干分鐘無流量時移除)。如果非 0(永久),技術規格所允許的範圍是 120(2 分鐘)到 86400 秒(24 小時)。

可能的返回值見 UPNPResult。


void clear_devices() 🔗

清除已發現裝置的列表。


int delete_port_mapping(port: int, proto: String = "UDP") const 🔗

如果預設閘道器上存在對給定埠和協定組合的埠對應,則將其刪除(見 get_gateway())。port 必須是 1 和 65535 之間的有效埠,proto 可以是 "TCP" 或 "UDP"。拒絕的原因可能是對應指向其他位址、埠為公認埠(1024 以下)、對應不是由 UPnP 新增的。可能的返回值見 UPNPResult。


int discover(timeout: int = 2000, ttl: int = 2, device_filter: String = "InternetGatewayDevice") 🔗

發現本地的 UPNPDevice。清除先前發現的裝置的列表。

預設情況下會篩選 IGD(InternetGatewayDevice)型別的裝置,因為這些裝置管理埠轉發。timeout 是等待回應的時間,單位為毫秒。ttl 是生存時間;請在你知道自己在做什麼的時候才碰這個參數。

可能的返回值見 UPNPResult。


UPNPDevice get_device(index: int) const 🔗

返回給定 index 處的 UPNPDevice。


int get_device_count() const 🔗

返回已發現的 UPNPDevice 的數量。


UPNPDevice get_gateway() const 🔗

返回預設閘道器。這是第一個發現的UPNPDevice,也是一個有效的IGD(InternetGatewayDevice)。


String query_external_address() const 🔗

返回預設閘道器的外部 IP 位址字串(見 get_gateway())。錯誤時返回空字串。


void remove_device(index: int) 🔗

將 index 處的裝置從已發現的裝置列表中移除。


void set_device(index: int, device: UPNPDevice) 🔗

將 index 處的裝置從已發現的裝置列表中設定為 device。