直接给出结论:在 Swoole 启动 TCP/HTTP 服务器时,host 参数决定了具体监听哪个协议栈。IPv4 和 IPv6 并非“自动兼容”或“同时启用”的关系,而是由你绑定的地址显式控制。默认的 0.0.0.0 仅监听 IPv4,:: 才监听 IPv6,想要混合使用?需要额外处理。

listen host 参数需明确指定 IPv4 或 IPv6 地址
Swoole 的 Server->addlistener() 或构造函数中的 host 字段,不支持模糊值(例如 "localhost" 或空字符串),也不会自动降级。它直接传递给底层的 bind() 系统调用。因此:
host => "0.0.0.0":仅绑定 IPv4 任意地址,不会接收 IPv6 连接,即使系统已开启 IPv6 且双栈模式已启用。host => "::":绑定 IPv6 任意地址,可以接受 IPv4 映射地址(例如::ffff:192.168.1.100),但前提是内核启用了net.ipv6.bindv6only = 0(大多数 Linux 发行版默认值为 0)。host => "127.0.0.1"和host => "::1"严格分离:前者只响应 IPv4 回环,后者只响应 IPv6 回环。
同时监听 IPv4 和 IPv6 需手动调用 addlistener 两次
Swoole 没有内置的“双栈”单 socket 监听模式——即一个 socket 同时处理 IPv4 和 IPv6 流量。很多开发者误以为设置 host => "::" 就能“通吃”,结果发现 IPv4 客户端无法连接。尤其在容器或某些云主机上,bindv6only 被设为 1 时,:: 就真的只接收 IPv6 了。
正确的做法是显式添加两个监听器:
$server = new SwooleHttpServer("::", 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP6);
$server->addlistener("0.0.0.0", 9501, SWOOLE_SOCK_TCP); // 注意:端口必须相同
- 第一个监听器使用
SWOOLE_SOCK_TCP6+"::",处理 IPv6 流量。 - 第二个使用
SWOOLE_SOCK_TCP+"0.0.0.0",处理 IPv4 流量。 - 两个监听器共享同一个
port,Swoole 内部会分别管理文件描述符,但业务逻辑共用同一个onRequest回调。
客户端获取真实 IP 时,remote_ip 字段行为存在差异
使用双监听时,$request->server['remote_addr'] 返回的 IP 字符串格式,取决于连接经过哪个 socket:
- IPv4 连接:返回点分十进制格式,例如
"192.168.1.100"。 - IPv6 连接:返回冒号十六进制格式,可能包含压缩,例如
"2001:db8::1"或完整形式"2001:0db8:0000:0000:0000:0000:0000:0001"。 - 如果启用了 IPv4 映射 IPv6 地址(
::ffff:192.168.1.100),Swoole 默认不会自动转换为 IPv4 格式,remote_addr直接返回映射地址字符串。
处理此问题时,建议不要依赖字符串前缀,而是直接使用 PHP 原生函数,更加可靠:
if (filter_var($request->server['remote_addr'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
// IPv4
} elseif (filter_var($request->server['remote_addr'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// IPv6
}
SSL/TLS 配置下 IPv6 监听需关注证书 SAN
如果使用 ssl 选项启动 HTTPS 服务并监听 "::",客户端通过 IPv6 访问时,TLS 握手仍然依赖证书中的 Subject Alternative Name(SAN)。这里有几个常见陷阱:
- 证书只包含
DNS:example.com,未添加IP:2001:db8::1——IPv6 直连会触发证书域名不匹配警告。 - Let’s Encrypt 等 ACME 服务商不签发纯 IP 的 SSL 证书(私有 CA 除外),因此生产环境几乎不会为 IPv6 地址单独配置证书。
- 实际部署中,应当让 DNS 同时解析 A 和 AAAA 记录,客户端通过域名访问,由证书的 DNS SAN 覆盖,而非依赖 IP 直连。
换句话说:HTTPS + IPv6 的正确做法是“使用域名访问”,而不是“使用 IPv6 地址直连”。监听 "::" 仅实现网络层支持,应用层的信任链仍然依赖 DNS 解析。
