ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

WSL2与Windows网络互通:localhost、端口转发与NAT全解析

WSL2与Windows网络互通:localhost、端口转发与NAT全解析 大家可能都有过这种经历在 WSL 里装好了 Redis 或者 Elasticsearch服务日志都显示在监听了可 Windows 这边的程序就是连不上 localhost:6379反过来Windows 上跑了 MySQLWSL 里怎么都连不进 127.0.0.1:3306。这些问题表面看起来是“网络配置不对”但归根到底是对 WSL 与 Windows 的网络栈到底怎么“共用”这件事没搞透。这篇内容就是来把这个底层故事讲清楚的。适合所有在 Windows 上做开发、用 VSCode Remote-WSL、跑 Docker Desktop、或者在 WSL 里折腾过数据库和本地服务的读者无论你是刚装好 WSL 的新手还是已经被端口转发折磨过几轮的“伪老鸟”。1. 先分清 WSL1 和 WSL2网络行为完全不同很多困惑都源于此好多人在查“WSL 网络问题”时在网上看到的第一句话就是“你先看下自己用的是 WSL1 还是 WSL2”但很少有人解释为什么这个版本号这么关键。它俩的网络栈实现完全是两条路后面所有互通、端口转发、防火墙问题都是从这个分叉长出来的。1.1 一条命令判断当前 WSL 版本判断方法很简单。在 PowerShell 或者 CMD 里敲wsl -l -v正常会输出类似这样的表格NAME STATE VERSION * Ubuntu Running 2 docker-desktop Stopped 2VERSION 列是 1 就是 WSL1是 2 就是 WSL2。如果这条命令直接报错或者不支持-v参数说明系统的 WSL 组件太旧先按第 5 章的思路更新。这里有个容易忽略的细节输出里每一个发行版都要单独看。你装了 Ubuntu 是 WSL2不代表 docker-desktop 也是 WSL2如果某个发行版还是 WSL1它的网络行为会和旁边的发行版完全不同排查时很容易被带偏。1.2 WSL1 的“真共享”没有虚拟网卡IP 就是同一个WSL1 的实现思路不是启动虚拟机而是把 Linux 的系统调用翻译成 Windows NT 内核调用。在这个模型下WSL1 根本没有自己的网络硬件它在用户态看到的网络接口就是 Windows 的物理网卡和虚拟网卡。所以在 WSL1 里执行ip addr看到的 IP 和 Windows 上的完全一样localhost 天然就是同一个 loopback不存在转发不转发的问题。这是不是听着很完美但 WSL1 的局限也出在这里。因为内核不是真的 Linux 内核很多需要真正网络协议栈支撑的功能在 WSL1 里是残缺的比如 iptables 基本不可用、tcpdump 抓包经常抓到一堆怪东西、Docker 容器因为无法创建 Linux 网络命名空间而跑不起来。它的“共用网络栈”是共享了但共享的是 Windows 的协议栈而不是 Linux 的这对网络开发场景来说远远不够。1.3 WSL2 的“虚拟共享”一个带完整内核的轻量虚拟机WSL2 换了一种完全不同的实现在 Hyper-V 虚拟化平台上跑一个轻量级工具虚拟机内部是一个真正的 Linux 内核拥有完整的网络协议栈。启动 WSL2 后Windows 上会多出一张名为 vEthernet (WSL) 的虚拟网卡WSL2 内部则是另一套 IP通常是 172.x.x.x 的私有网段。从协议层面讲WSL2 和 Windows 已经是两台“机器”了通信方式就是 NATWSL2 的流量先到虚拟交换机由 Windows 做 NAT 转发出去。但微软为了让用户感觉不到这个边界又加了一层 localhost 转发让 Windows 能通过 localhost 访问 WSL2 里的服务。这就是“共用网络栈”这个标题里最容易被误解的部分——它不是真共享而是用一层转发机制“做”出来的共享体验。对比项WSL1WSL2网络栈归属Windows NT 网络栈独立 Linux 内核网络栈IP 地址与 Windows 相同独立的 172.x.x.x 私有 IPlocalhost 互通天然互通依赖 localhost 转发机制iptables/tcpdump基本不可用完整可用Docker 支持不支持容器网络命名空间支持完整 Docker 场景至于微软为什么默认用 NAT 而不是桥接原因也很实际NAT 模式隔离性好WSL2 拿到的 IP 不会和办公室局域网里的真实设备冲突也不用在每次接入不同 Wi-Fi 时重新分配 IP安装部署成本最低。缺点就是前面说的外部设备访问不进来得靠端口转发这类手段补。2. localhost“通”与“不通”的真正逻辑谁在转发、转到了哪里搞清版本差异后接下来是最容易让人发懵的部分localhost 到底是怎么通的为什么有时候通有时候不通这一章我把两个方向分别说透。2.1 Windows 到 WSL2自动端口转发但有两个前提在 WSL2 里启动一个服务比如 Redissudo apt install redis-server sudo service redis-server start ss -lntp | grep 6379如果看到它监听在 0.0.0.0:6379那么 Windows 侧直接redis-cli -h localhost -p 6379一般就能连上。这个体验看起来和 WSL1 一样但背后是 Windows 在 WSL2 启动时自动把 localhost 的流量转发到了 WSL2 对应的端口上。用端口转发工具的概念来理解也差不多localhost 转发就是微软内置的一个自动端口映射器。这套转发要生效有两个前提经常被忽略。第一WSL2 发行版必须处于运行状态转发进程是在发行版启动时才建立的如果你用wsl --terminate Ubuntu把发行版停了Windows 侧的 localhost 转发也随之失效。第二WSL2 里的服务不能只监听了 IPv6 的 ::1也不能只监听某个具体的容器 IP。排查时先看ss -lntp确认监听地址是 0.0.0.0这是所有“能通”的前提。2.2 WSL2 到 Windows不是每种服务都能通过 localhost 反向访问很多人在 WSL2 里想连 Windows 上装的 MySQL 或 PostgreSQL习惯性地连localhost:3306。这个能不能通取决于 Windows 侧服务的绑定地址和防火墙策略。如果 Windows 上的 MySQL 配置了bind-address0.0.0.0WSL2 里mysql -h localhost通常是能连上的。但 Windows 上不少原生服务默认只绑定 127.0.0.1这种情况下从 WSL2 访问 localhost 是否成功表现并不稳定和服务的实现、Windows 防火墙策略都有关系。与其赌运气不如直接用 Windows 主机在虚拟网卡上的 IP。在 WSL2 里执行ip route show | grep -i default | awk { print $3 }或者cat /etc/resolv.conf | grep nameserver | awk { print $2 }拿到的地址通常是 Windows 主机的 172.x.x.1用这个地址去连 Windows 上的服务绕开了 localhost 转发的各种隐含条件直击 NAT 网关。开发环境里把数据库连接串里的 host 改成这个地址是目前最省心、最可控的方案。2.3 端口冲突时转发就是一场暗战localhost 转发还有一类很隐蔽的故障Windows 本机已经占了同一个端口。比如你在 Windows 上手动装了一个 Redis 占着 6379WSL2 里又起了一个 Redis这时候 Windows 侧访问 localhost:6379 走的可能是 Windows 自己的进程而不是 WSL2 里的。最直接的现象就是“两边都起了服务但连到的好像不是我以为的那个”。排查时先看 Windows 侧netstat -ano | findstr :6379如果这个端口被某个 PID 占用再判断是不是 WSL 的转发占位。通常 Windows 本机服务优先级更高WSL2 里的服务就“隐形”了。这种情况要么换个端口要么停掉 Windows 侧同端口的服务不要指望通过 WSL 侧改监听来解决问题因为转发优先级根本不在你这边的控制范围里。3. 真正常用的“共用”方案从双向往返到局域网访问原理讲完落地才是重点。这一章我按三个开发中最常见的场景给出可以直接照抄的配置外加一个进阶的镜像网络模式。3.1 场景一WSL2 里跑 Redis、ElasticsearchWindows 程序直接连这个场景对本地开发最实用。你不必在 Windows 上装一堆绿色版数据库把服务集中在 WSL2 里更好管理磁盘和内存也相对可控。以 Redis 为例sudo apt update sudo apt install redis-server sudo service redis-server start启动后务必确认监听地址ss -lntp | grep 6379输出里应该是*:6379或0.0.0.0:6379。之后 Windows 上随便用什么客户端连localhost:6379都能通。Elasticsearch 同理在 WSL2 里下载解压后直接./bin/elasticsearch启动Windows 浏览器访问http://localhost:9200就能看到版本信息。这里有一个很值得记住的经验只要服务监听在 0.0.0.0Windows 侧通过 localhost 访问基本没毛病。但如果服务为了安全配置成只监听 127.0.0.1那 localhost 转发也救不了你外部访问一律失败。所以开发环境里宁可在 WSL2 里监听 0.0.0.0再用 Windows 防火墙做访问限制也别在生产式安全配置上浪费调试时间。3.2 场景二WSL2 里访问 Windows 上的服务反过来如果你习惯把数据库装在 Windows 原生环境WSL2 里跑应用代码那需要让 WSL 能连到 Windows 的数据库端口。步骤很简单Windows 上的服务确认监听 0.0.0.0Windows 防火墙放行对应端口比如 3306WSL2 里通过主机 IP 访问# 获取主机 IP win_ip$(ip route show | grep -i default | awk { print $3 }) mysql -h $win_ip -P 3306 -u root -p这里建议不要直接用localhost。虽然部分配置下也能通但一旦 Windows 服务绑定策略变化或者 Windows 防火墙对转发链路有新的弹窗限制查起来非常痛苦。直接在代码配置里写死主机 IP反而是最不容易出幺蛾子的方案。3.3 场景三让局域网手机、另一台电脑访问 WSL2 服务WSL2 默认 NAT 模式决定了外部设备无法直接访问需要用 Windows 做一次端口转发。假设 WSL2 里跑了一个 Web 服务监听 8080先拿到 WSL2 当前的 IPwsl hostname -I假设输出是 172.22.200.150。然后在 Windows 管理员 PowerShell 里执行netsh interface portproxy add v4tov4 listenport18080 listenaddress0.0.0.0 connectport8080 connectaddress172.22.200.150再添加防火墙规则New-NetFirewallRule -DisplayName WSL Port Forward -Direction Inbound -LocalPort 18080 -Protocol TCP -Action Allow之后同一局域网内的手机或同事电脑就能通过你 Windows 主机的局域网 IP 加 18080 端口访问到 WSL2 里的服务了。这个方案最烦的地方是 WSL2 的 IP 每次重启可能变。我自己有两个应对办法一是写一个 PowerShell 脚本先动态解析 WSL2 的 IP 再更新 portproxy 规则二是直接跳到下一节的 mirrored 模式从根源上消灭 IP 漂移问题。3.4 进阶mirrored 网络模式让 WSL 真正“共用”Windows 网卡如果你用的是 Windows 11 22H2 以上系统WSL 版本也在 2.0 以上可以试试镜像网络模式。在用户目录下编辑.wslconfig文件[wsl2] networkingModemirrored然后执行wsl --shutdown重启 WSL。在这个模式下WSL2 直接共享 Windows 的物理网卡和 IPlocalhost 互通更全面还支持 IPv6、mDNS、组播等以前 NAT 模式下做不到的特性。对我来说最大的红利是局域网访问不再需要 netsh 端口转发了WSL2 里监听的端口直接在局域网可见减少了一层中转链路。不过它也不是没坑。按我遇到过的实际情况某些带网络过滤功能的办公网络软件会和 mirrored 模式的虚拟网卡互相干扰导致 WSL2 断网个别旧版本 Docker Desktop 也会在 mirrored 模式下出现容器端口映射异常。所以这个特性适合追求“零感知网络栈”的本地开发但如果你的办公网络环境比较复杂反而是默认 NAT 模式更稳。4. Docker Desktop 的 WSL 后端容器网络如何参与这场“共用”Docker Desktop 是另一个离不开 WSL 网络栈的典型场景而且它的网络链路比普通 WSL2 服务更绕值得单独讲。4.1 Docker Desktop 为什么把引擎跑到 WSL2 里面Docker Desktop 支持两种后端老牌 Hyper-V 和后起的 WSL2。WSL2 后端的做法是Docker Desktop 自己创建两个发行版——docker-desktop 和 docker-desktop-data实际运行 Docker 引擎和存储的就是 docker-desktop 这个发行版。你用哪个发行版跑docker命令不重要最终都通过 Docker context 指向 docker-desktop 里的 daemon。为什么现在推荐直接选 WSL2 后端因为它的启动速度比 Hyper-V 快得多内存占用也更低而且 Docker 容器里的文件可以和 Windows 文件系统直接互通开发体验连贯很多。代价就是排障时网络链路更长了从 Windows localhost 到容器中间经过了 Windows 的端口代理、docker-desktop 虚拟机的 NAT、容器网络的 bridge任一层出问题都会表现为“端口不通”。4.2 容器端口与 Windows 的互通localhost 转发的叠加效果在 Windows 上执行docker run -d -p 8080:80 nginx然后浏览器访问http://localhost:8080大多数情况下能直接看到 nginx 欢迎页。这条链路实际是Windows localhost:8080 被 WSL 的 localhost 转发到 docker-desktop 发行版docker-desktop 内部的端口代理再把 8080 转给 bridge 网络里的 nginx 容器。两个转发机制叠加最终在用户侧做到了“无感”。也正因为是两层转发叠加排查时很容易分不清问题在哪一层。我自己常用的办法是从内向外拆先在 docker-desktop 发行版里用docker ps确认容器在跑然后进入容器测试端口再测 Windows localhost 端口是否被监听最后看防火墙。一层层缩小范围而不是看到一个端口不通就盲目重启 Docker Desktop。4.3 在 WSL2 用户发行版里跑 Docker 时网络到底走哪套如果你没用 Docker Desktop而是在 WSL2 用户发行版里直接装了 Docker Engine那网络又是另一套逻辑Docker 引擎就在当前发行版里容器通过它在自己的网络栈里创建 bridge 网络Windows 侧访问容器端口并不会自动走 localhost 转发你需要自己确保容器启动时用-p映射到 WSL2 的 0.0.0.0然后再借助第 3 章说到的 localhost 转发或端口转发让 Windows 能访问到。这里有个最容易踩的坑在用户发行版里装 Docker Engine 时如果发行版里没有启用 systemdservice docker start能启动但网络可能异常比如容器 IP 分配不出来。WSL2 默认发行版在较新版本已经支持 systemd但老一点的安装方式需要手动在/etc/wsl.conf里加配置。放着默认 UI 不折腾这是很多 WSL 老玩家也翻过车的地方。5. 高频网络故障排查从安装卡死到服务连不上最后一部分集中写 WSL 网络相关的高频报错和处理思路覆盖我从实际项目里遇到最多的几类问题。5.1 wsl --install 太慢或卡住不要傻等换个下载路径很多新手是在wsl --install这一步开始痛苦的进度条半天不动很正常。原因是发行版镜像体积本来就大加上网络链路波动很容易卡在下载阶段有时还会直接冒出一个 403 之类的 HTTP 下载错误。遇到这种情况不要反复重装那不是系统坏了是下载链路的问题。应对方式有几个按效率排序用wsl --update --web-download更新 WSL 组件有时能避开默认渠道不稳定的问题。去微软官方 Ubuntu 页面或 GitHub Releases 页面手动下载离线安装包比如.wsl文件或.msixbundle再用wsl --install --from-file命令安装指定发行版下载过程可中断续传比在安装器里干等可控得多。安装完发行版后第一件事就是把 apt 源换成国内镜像源比如清华 TUNA 或阿里云镜像后续装软件、装工具的速度会质变也能避免很多“网络超时”带来的误判。如果 Windows 的 DNS 解析有问题把 DNS 改成公共 DNS比如 223.5.5.5 或 119.29.29.29也值得一试。5.2 “your version of WSL is too old” 是什么意思这个报错通常出现在运行一些依赖新版 WSL 功能的命令或工具时比如某些 WSL2 扩展工具、新版内核特性、或者 Docker Desktop 的 WSL 集成。我在给 WSL 装 CUDA 补丁包时也撞到过这个报错字面意思很直白当前 WSL 组件版本太旧需要更新。处理方式wsl --update wsl --version如果wsl --update也失败同样可以尝试加--web-download参数或者直接到微软官方 GitHub Releases 手动下载并安装新版本。安装完后重启所有 WSL 终端再确认wsl --version输出的内核版本号。这个报错在定制版系统和企业镜像环境里特别常见因为组件版本可能被长期冻结建议升级一次之后养成定期更新的习惯。5.3 服务连不上的系统排查顺序遇到“WSL 里的服务 Windows 访问不了”“Windows 的服务 WSL 里访问不了”这类问题从外往里拆是最有效的思路先确认 WSL 发行版本身在运行wsl -l -vSTATE 列不是 Running 就先启动。确认服务在 WSL 内确实监听正确端口和地址ss -lntp监听地址必须是 0.0.0.0 或至少*。在 Windows 侧测 localhost 端口Test-NetConnection localhost -Port 6379通了说明转发链路没问题不通则继续往下。查防火墙Windows 防火墙在首次通信时可能弹窗被忽略后端口就静默不通WSL 发行版内部一般不用额外配防火墙。查端口占用netstat -ano | findstr :端口如果有非 WSL 进程占用按第 2 章的情况处理。局域网场景还要查 portproxy 规则是否因 WSL IP 变化而失效netsh interface portproxy show all。这套顺序我用了挺久基本能覆盖八成以上的“连不上”问题而且每一步都能很快定位出问题归属层避免在错误方向浪费时间。5.4 顺手聊两句 VSCode 和 binwalk 的场景最后说两个和网络栈相关的小实战都是我从日常开发里觉得值得分享的。VSCode 的 Remote-WSL 扩展本质是通过内部管道的 IPC 在 Windows 和 WSL2 之间通信不依赖网络端口所以你在 VSCode 里打开 WSL 窗口时网络栈是否配置正确几乎不影响。但如果你用 VSCode 的“端口转发”面板把 WSL2 里的服务端口暴露到 Windows 侧它走的恰恰就是那套 localhost 转发机制端口被占用了、服务没启动都会直接显示失败。这算是“网络栈共用”在开发工具体验里最典型的表现。另一个场景是 WSL 里跑 binwalk 这类固件分析工具。很多人不知道直接在 WSL 里用/mnt/c/xxx.bin这样的路径就能处理 Windows 盘符里的文件不需要在两个系统之间拷来拷去。这在分析流程里省了很多事也是 WSL 和 Windows “共用”精神的一种体现——文件系统共用、网络栈共用最终都是为了让你不用在两种操作系统之间来回切换。从我自己的使用习惯来说现在本地开发环境是这么搭的WSL2 里跑 Redis、Elasticsearch 和编译工具链Windows 只留 IDE、浏览器和必要的原生客户端网络配置用默认 NAT 加少数几个 localhost 方案基本能覆盖九成场景。只有需要给同事演示或者用手机联调时才临时切到 mirrored 模式或加一条 netsh 转发。这套“Windows 当桌面WSL2 当服务器”的组合折腾成本最低出问题也最好排查。最后再提醒一句上面所有的共享、转发、镜像模式都是为本地开发服务的生产环境别指望用这套东西。真到服务器上老老实实用正常发行版网络栈配置就好。
返回列表