UPNP

继承: RefCounted < Object

通用即插即用(UPnP)功能,用于网络设备的发现、查询及端口映射。

描述

此类可用于发现本地网络上兼容的 UPNPDevice并对其执行命令,例如管理端口映射(用于端口转发/NAT 遍历)和查询本地和远程网络 IP 地址。请注意,此类上的方法是同步的,并阻止调用线程。

要转发特定端口(此处为7777,请注意discover()add_port_mapping()都可以返回应检查的错误):

::

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

要关闭特定端口(例如,在您使用完毕后进行操作):

upnp.delete_port_mapping(port)

注意: UPNP 发现功能会阻塞当前线程。若要不阻塞主线程而执行发现操作,请以以下这种方式使用 Thread:

# 当 UPnP 端口映射设置完成时发出此信号(无论设置过程是否成功或失败)。
signal upnp_completed(error)

# 请将此内容替换为您自己在 1024 至 65535 范围内的服务器端口号。
const SERVER_PORT = 3928
var thread = null

func _upnp_setup(server_port):
    # UPNP 查询需要一些时间。
    var upnp = UPNP.new()
    var err = upnp.discover()

    if err != OK:
        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(OK)

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

func _exit_tree():
    # 在此等待线程完成运行,以便在该线程运行期间处理应用退出事宜。
    thread.wait_to_finish()

术语: 术语:在 UPnP 网络的语境中,“网关”(或“互联网网关设备”,简称 IGD)指的是能够让本地网络中的计算机接入互联网(“广域网”,WAN)的网络设备。这些网关通常也被称为“路由器”。

常见问题:

  • 如上所述,这些调用是阻塞式的,不应在主线程中运行,尤其是因为它们可能会一次性阻塞数秒之久。请使用线程!

  • 网络是物理性的且十分复杂混乱的。数据包在传输过程中可能会丢失或被过滤,地址、空闲端口和分配的映射可能会发生变化,而且设备随时都可能离开或加入网络。请务必留意这一点,在检查和处理错误时要勤勉认真,并且如果可能的话,要妥善处理这些情况:添加清晰的错误界面、设置超时机制以及进行重试处理。

  • 端口映射可能会随时发生变化(甚至被删除),网关的远程/外部 IP 地址也可能随之改变。您应当考虑重新查询外部 IP 地址,并尝试定期(例如每 5 分钟一次以及在网络出现故障时)更新/刷新端口映射。

  • 并非所有设备都支持 UPnP 功能,而且有些用户会禁用 UPnP 支持。您需要对此进行处理(例如,记录相关事项并要求用户手动设置端口转发,或者添加其他网络穿越方法,比如中继/镜像服务器、NAT 穿越技术(如 STUN/TURN)等)。

  • 考虑一下在出现冲突时会发生什么情况。也许同一网络中的多个用户都想在同一时间玩你的应用,又或者另一个应用程序使用了相同的端口。将端口设置为可配置的,并且最好能自动选择一个端口(在失败时尝试使用不同的端口)。

进一步阅读建议: 如果您想更深入地了解 UPnP(以及具体的互联网网关设备(IGD)和端口控制协议(PCP)), Wikipedia 是一个很好的起点,其规范可在 Open Connectivity Foundation 上找到,而 i3D 的实现则是基于 MiniUPnP client

属性

方法

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 🔗

会添加一条映射规则,用于将默认网关(见 get_gateway() )上的外部 port 范围在 1 到 65535 之间,但建议使用 1024 及以上的端口)转发到本地机器上的 port_internal (具体端口取决于所选协议 proto ("TCP" 或者 "UDP", 默认是 "UDP")如果该网关设备上已经存在针对给定端口和协议组合的端口映射,则此方法会尝试覆盖它。如果不想这样做,您可以手动使用 get_gateway() 获取网关,并在它可用的情况下调用 add_port_mapping() 请注意,使用 UPnP 将熟知端口(低于 1024)进行转发可能会因设备而异而失败。

根据网关设备的不同情况,如果该端口已有相应的映射设置,那么该设置要么会被更新,要么会因存在冲突而拒绝执行此命令,尤其是如果该端口的现有映射并非通过 UPnP 创建的,或者指向的网络地址(或设备)与当前设备不同的话。

如果 port_internal0 (默认值),那么外部端口和内部端口(即 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(互联网网关设备)类型的设备,默认情况下会启用过滤功能,因为这些设备负责端口转发。 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