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.
Checking the stable version of the documentation...
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 客户端。
属性
|
||
|
||
|
方法
void |
add_device(device: UPNPDevice) |
add_port_mapping(port: int, port_internal: int = 0, desc: String = "", proto: String = "UDP", duration: int = 0) const |
|
void |
|
delete_port_mapping(port: int, proto: String = "UDP") const |
|
discover(timeout: int = 2000, ttl: int = 2, device_filter: String = "InternetGatewayDevice") |
|
get_device(index: int) const |
|
get_device_count() const |
|
get_gateway() const |
|
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
未知错误。
属性说明
如果为 true,则 IPv6 用于 UPNPDevice 发现。
如果为 0,系统会自动选择用于发现的本地端口。如果为 1,将从源端口 1900 进行发现(与目的端口相同)。否则,将使用该值作为端口。
String 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。