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))
emit_signal("upnp_completed", 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")
emit_signal("upnp_completed", OK)
func _ready():
thread = Thread.new()
thread.start(_upnp_setup.bind(SERVER_PORT))
func _exit_tree():
# 游戏退出但线程还在运行时,在此处等待线程完成。
thread.wait_to_finish()
术语:UPnP 网络中,“网关”(gateway,或称“互联网网关设备”,internet gateway device,简称 IGD)指的是在局域网中让计算机能够访问互联网(“广域网”,wide area network,WAN)的网络设备。这些网关经常也叫做“路由器”。
陷阱:
前文解释过,这些调用都是阻塞的,不应该在主线程上执行,一次就能阻塞上很多秒。用用线程吧!
网络是实打实的混乱。数据包可能会在传输过程中丢失或者被过滤掉,地址、空闲端口、端口映射有可能发生变化,设备可以随时离开或者加入网络。请考虑周全,老老实实地检查错误并进行处理,处理错误时请尽量友好:添加简洁的报错 UI、超时处理、重试机制。
端口映射是随时会变的(也可以被删除),网关的远程/外部 IP 也可能发生改变。你应该考虑定期重新查询外部 IP、尝试更新/刷新端口映射(例如每隔 5 分钟或者在发生网络错误时执行)。
并不是所有的设备都支持 UPnP,有些用户还会禁用 UPnP 支持。你需要处理这种情况(例如编写文档,要求用户手动进行端口映射,或者加入接力/镜像服务器、NAT 打洞、STUN/TURN 等 NAT 穿透的备用方案)。
请考虑映射冲突时该怎么办。可能在同一个网络上同时有多个用户想要来玩你的游戏,或者有其他应用程序用了一样的端口。请把端口号做成可配置的,最好能够自动选择(失败时重试其他端口)。
拓展阅读:如果你想了解更多关于 UPnP(尤其是 Internet Gateway Device(IGD)和 Port Control Protocol(PCP)),可以首先查看维基百科,技术规范可以在 Open Connectivity 基金会找到,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
。