写爬虫、外网做内网穿透、外网或者调试微信回调接口时,外网突然发现自己需要知道服务器的外网公网IP。Java里没有现成的外网API,你只能自己想办法。外网最直接的外网方式是访问第三方服务,比如 checkip.amazonaws.com 或者 ipify.org,外网它们会返回一串纯文本IP。外网
代码写起来倒是外网简单:
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; public class PublicIp { public static void main(String[] args) throws Exception { URL url = new URL("https://api.ipify.org"); BufferedReader reader = new BufferedReader( new InputStreamReader(url.openStream()) ); String ip = reader.readLine(); System.out.println("外网IP: " + ip); reader.close(); } } 这段代码能跑,但有个坑——openStream() 会阻塞,外网如果网络慢,外网你的外网主线程就卡死了。真实项目里必须放在线程池里,外网或者用 HttpClient 配合超时设置。HttpClient 是Java 11以后推荐的方式,代码稍微长一点,但更可控。
还有个问题,这些公共服务并不总是稳定。有一次我连着调了三次 ipify.org,返回结果分别是三个不同的IP——原来是服务商做了负载均衡,每次请求可能打到不同出口节点。如果你对IP准确性要求高,最好同时请求两三个服务,对比结果,取出现次数最多的那个。
如果你不想依赖外部服务,也可以自己搭建一个HTTP接口,返回请求方的IP。比如用Spring Boot写个 @RestController,方法里取 request.getRemoteAddr()。但注意,如果服务器前面有Nginx或SLB,你拿到的可能是代理的IP,而不是真实客户端IP。这时需要Nginx配置 X-Forwarded-For 头,然后Java里这样解析:
String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.isEmpty()) { ip = request.getRemoteAddr(); } else { // 多个IP时取第一个,这是惯例 ip = ip.split(",")[0].trim(); } 不过这个方案有个隐患——如果请求直接打到服务器,没经过代理,客户端可以伪造 X-Forwarded-For 头,导致你拿到的IP是假的。所以生产环境里,你得确保只有信任的代理服务器才能设置这个头,否则就老老实实用 getRemoteAddr()。
再提一个冷门的坑:IPv6。现在很多云服务器默认开启了IPv6,但你的公网IP可能是IPv6地址,形如 240e:390:...:1234。而第三方服务有的只返回IPv4,有的会返回IPv6。如果你的需求是IPv4,得在请求时强制指定IP协议版本。用 HttpClient 的话,可以设置 SocketOption 或者直接解析返回结果,用正则过滤掉冒号。
最后说个我自己踩过的坑。有一次部署到Kubernetes集群里,Pod访问外网IP,结果返回的是节点IP,不是Pod的IP。因为Pod的网络出口走的是宿主机NAT。这种场景下,你获取的“外网IP”其实代表整个集群的出口,而不是单个Pod。如果业务逻辑需要区分不同Pod,就得额外传参或者用其他标识。
总结下来,Java获取外网IP没有银弹。小型工具用 ipify.org 最省事,生产环境建议自己搭接口并做好代理头处理,特别注意IPv6和集群环境。代码量不大,但细节决定你是否会在半夜被叫起来修bug。