PHP中获取IP版本需先确定来源(如$_SERVER['REMOTE_ADDR']或HTTP_X_FORWARDED_FOR),再用filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4/IPv6)精确判断,不可依赖默认行为或混用ip2long/inet_pton。

PHP中获取IPv4和IPv6地址,本质不是“怎么取”,而是“从哪取、取到什么、怎么判”——默认$_SERVER['REMOTE_ADDR']可能返回任意版本,且无法直接区分。
为什么$_SERVER['REMOTE_ADDR']不能直接告诉你IP版本
这个超全局变量只返回客户端原始连接地址,由Web服务器(如Nginx/Apache)或反向代理注入。它不关心协议,也不做转换:可能是"192.168.1.100",也可能是"2001:db8::1",甚至在双栈代理下混用IPv4-mapped IPv6格式(如::ffff:192.168.1.100)。你拿到的只是一个字符串,版本信息必须自己验证。
- 不校验就直接
ip2long(),遇到IPv6会返回false,且无警告 - 用
filter_var($ip, FILTER_VALIDATE_IP)时,必须显式指定FILTER_FLAG_IPV4或FILTER_FLAG_IPV6,否则默认只认IPv4 - 若前端有CDN或Nginx配置了
real_ip_header X-Forwarded-For,REMOTE_ADDR可能只是CDN内网地址,真实IP藏在HTTP_X_FORWARDED_FOR里——而后者可能是逗号分隔的多个IP,含IPv4/IPv6混合
filter_var是判断IP版本最可靠的方式
它基于RFC标准校验,比正则或strpos()查冒号更健壮,且能处理IPv6压缩写法(如::1)、IPv4映射格式(::ffff:10.0.0.1)等边界情况。
- 判IPv4:
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) - 判IPv6:
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) - 想同时支持双栈?别用
FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6——这会返回false,因为标志位是互斥的;应分别调用两次判断 - 注意:
FILTER_FLAG_NO_PRIV_RANGE和FILTER_FLAG_NO_RES_RANGE可额外过滤私有/保留地址,但不影响版本识别
ip2long和inet_pton不能混用,且有平台陷阱
这两个函数是协议专属的:一个为IPv4设计,一个为IPv4/IPv6通用但返回二进制。强行跨用会导致静默失败或错误结果。
立即学习“PHP免费学习笔记(深入)”;
-
ip2long("::1")→false(不是报错,是返回false,容易被忽略) -
inet_pton("192.168.1.1")→ 返回4字节二进制,可用strlen()判长度:strlen() === 4是IPv4,=== 16是IPv6 - 32位系统下
ip2long返回负数问题仍存在,必须用sprintf('%u', ip2long($ip))转无符号整型,否则存入MySQLINT UNSIGNED会出错 -
inet_pton在Windows上对IPv6支持较晚(PHP 5.3+),旧环境需确认
双栈环境下真正要防的是“假IPv6”和“伪造头”
很多所谓“获取IPv6”的代码,其实只是把HTTP_X_FORWARDED_FOR第一个IP拿过来判一下就完事——但攻击者可以轻易伪造该Header。生产环境必须结合可信来源(如Nginx的set_real_ip_from白名单)和协议一致性检查。
- 若Web服务器监听IPv6套接字,但客户端走的是IPv4隧道(如DS-Lite),
REMOTE_ADDR仍是IPv4,不代表你没支持IPv6 - IPv4-mapped IPv6地址(形如
::ffff:a.b.c.d)本质是IPv4,filter_var用FILTER_FLAG_IPV6会通过,但业务逻辑中通常应视作IPv4处理 - 不要依赖
gethostbyname(gethostname())获取本机IP——它只返回首个A记录,大概率是127.0.0.1或内网IPv4,完全不可靠
真正关键的不是“取到哪个IP”,而是“这个IP是否来自可信链路、是否符合你当前业务对协议版本的实际要求”。版本判断只是起点,后续的存储、日志、限流、地理定位都得跟着协议特性走——比如IPv6地址不能塞进INT字段,子网匹配不能用ip2long位运算。











