apppublic/aidocs/uni_outip.md
2025-10-05 11:23:33 +08:00

6.5 KiB
Raw Permalink Blame History

外网 IP 获取工具技术文档

# 外网 IP 获取工具External IP Getter

一个基于多协议尝试获取外网公网 IP 地址的 Python 工具,支持 NAT-PMP、UPnP 以及第三方网页抓取方式。通过多进程隔离输出流的方式安全地获取 IP 地址。

---

## 目录

- [功能概述](#功能概述)
- [依赖库](#依赖库)
- [核心模块与函数说明](#核心模块与函数说明)
  - [`pmp_get_external_ip()`](#pmp_get_external_ip)
  - [`upnp_get_external_ip()`](#upnp_get_external_ip)
  - [`ipgetter_get_external_ip()`](#ipgetter_get_external_ip)
  - [`get_external_ip()`](#get_external_ip)
  - [`outip(w)`](#outipw)
  - [`get_ip()`](#get_ip)
  - [`run()`](#run)
- [主程序入口](#主程序入口)
- [使用示例](#使用示例)
- [注意事项](#注意事项)
- [未来优化建议](#未来优化建议)

---

## 功能概述

本工具旨在可靠地获取当前设备所在的公网 IP 地址,采用多种协议逐级回退策略:

1. **NAT-PMP**:适用于支持该协议的路由器(如部分 Apple 设备)。
2. **UPnP IGD**:通用即插即用互联网网关设备协议,广泛用于家庭路由器。
3. **IPGetter 网页抓取**:当本地网络协议不可用时,通过访问公网服务获取 IP。

为避免跨进程通信中的阻塞和异常影响主流程,使用 `multiprocessing.Pipe` 和子进程隔离执行 IP 获取逻辑。

---

## 依赖库

| 库名 | 用途 |
|------|------|
| `natpmp` | 实现 NAT-PMP 协议以获取公网 IP |
| `upnpclient` | 发现并调用 UPnP IGD 服务 |
| `appPublic.ipgetter` | 第三方 IP 获取器(基于网页抓取) |
| `multiprocessing` | 多进程支持,用于隔离 IO 操作 |
| `os`, `time` | 系统操作与延时控制 |

> ⚠️ 安装命令:
```bash
pip install natpmp upnpclient app-public

核心模块与函数说明

pmp_get_external_ip()

尝试通过 NAT-PMP 协议获取公网 IP。

返回值

  • 成功时返回字符串格式的公网 IP"203.0.113.45"
  • 失败或异常时返回 None

示例

ip = pmp_get_external_ip()
if ip:
    print("NAT-PMP 获取成功:", ip)

upnp_get_external_ip()

通过 UPnP IGDInternet Gateway Device协议从路由器获取公网 IP。

流程说明

  1. 调用 upnpclient.discover() 自动发现局域网内支持 UPnP 的网关设备。
  2. 提取第一个设备,并查找包含 'WAN''Conn' 的服务名(通常是 WANIPConnection 或类似)。
  3. 调用 GetExternalIPAddress() 方法获取外部 IP。
  4. 解析响应中 NewExternalIPAddress 字段。

返回值

  • 成功时返回公网 IP 字符串
  • 出错时打印错误信息并返回 None

📌 注意:若有多台 UPnP 设备,请注意选择正确的设备实例。


ipgetter_get_external_ip()

使用第三方网页服务获取公网 IP作为最后兜底方案。

特点

  • 使用 appPublic.ipgetter.IPgetter 类进行 HTTP 请求抓取。
  • 内置重试机制,每 0.1 秒尝试一次直到成功。
  • 忽略请求过程中的所有异常。

返回值

  • 成功获取后立即返回 IP 字符串
  • 不会永久阻塞

get_external_ip()

主协调函数:按优先级顺序尝试三种方法获取 IP。

执行顺序

  1. NAT-PMP
  2. UPnP
  3. Web 抓取IPGetter

一旦任一方法成功即返回结果,形成“短路”逻辑。

返回值

  • 公网 IP 字符串(推荐始终检查非空)
  • 若全部失败,则返回 None

outip(w)

在独立进程中运行的函数,将获取到的 IP 输出到指定写入端 w

参数

  • w: 可写的文件描述符管道(来自 Pipe() 的写入端)

原理

  • 使用 os.dup2(w.fileno(), 1) 将标准输出重定向至管道
  • 调用 get_external_ip() 并通过 print(ip) 输出
  • 子进程结束后,父进程可通过读取管道获得结果

优点:避免复杂对象传递,利用文本流简化 IPC。


get_ip()

主进程调用此函数来启动子进程并安全获取 IP。

步骤

  1. 创建匿名管道 Pipe()
  2. 打开读取端为文件对象
  3. 启动子进程执行 outip(w)
  4. 读取子进程输出的第一行
  5. 等待子进程结束
  6. 返回去除了换行符的 IP 字符串

返回值

  • 成功:公网 IP 字符串(如 "8.8.8.8"
  • 失败:空字符串(需判断有效性)

run()

持续运行的测试循环函数。

行为

  • 每 10 秒调用一次 get_ip()
  • 若获取到 IP则打印ip='203.0.113.45'
  • 使用 time.sleep(10) 控制频率

可用于长期监控公网 IP 变化。


主程序入口

if __name__ == '__main__':
    run()

当脚本直接运行时,进入无限循环模式,每隔 10 秒输出当前公网 IP。


使用示例

单次获取 IP

from your_module import get_ip

ip = get_ip()
print("Current Public IP:", ip)

集成进其他应用

可将 get_external_ip() 直接导入使用,无需多进程封装:

from your_module import get_external_ip

public_ip = get_external_ip()
if public_ip:
    print(f"Public IP is {public_ip}")
else:
    print("Failed to retrieve public IP")

注意事项

  1. 权限要求

    • UPnP/NAT-PMP 需要设备处于同一局域网且路由器启用相应功能。
    • 某些防火墙可能阻止 SSDPUPnP 发现协议)广播包。
  2. 性能与延迟

    • ipgetter_get_external_ip() 依赖网络请求,可能较慢。
    • 推荐仅作为备用手段。
  3. 稳定性问题

    • upnpclient.discover() 可能因网络波动返回空列表,导致索引错误。
    • 建议添加超时处理或重试逻辑。
  4. 多进程开销

    • get_ip() 每次创建新进程,不适合高频调用(如 >1次/秒)。
  5. 输出重定向风险

    • os.dup2 修改了子进程的标准输出,确保只在子进程中使用。

未来优化建议

改进项 描述
添加超时机制 给每个 IP 获取方法设置最大等待时间
缓存最近 IP 减少重复查询开销
支持更多协议 如 PCPPort Control Protocol
异常日志记录 替代 print(),使用 logging 模块
配置化策略 允许用户配置启用哪些协议及顺序
异步支持 使用 asyncio + aiohttp 提升效率

许可证

请根据项目实际情况补充 LICENSE 信息。

示例MIT License


--- 

✅ 文档完成。可保存为 `README.md` 或集成至 Sphinx/Docusaurus 文档系统。