当一名前端攻城狮把 WSL 作为主力开发系统之后……

9,474 阅读7分钟

就在刚才把一个 WSL 2 (下文中除了明确说明 WSL 1 以外,WSL 都表示 WSL 2)与 Windows 间端口通信的棘手问题解决了,便迫不及待也觉得是时候把这一两年来使用 WSL 的爱恨情仇好好记录一番了,方便自己未来回顾,也方便有缘人少踩一些坑。

多一嘴,以“WSL”或者“前端 WSL”搜索相关文章,从我的直观感受来看好像基本都是入门装个开发环境就完了?或者后端的使用更多?本着爱折腾的精神来看,这次我来好好分享一下自己的使用体验,后续应该还会在这里持续更新(但愿

WSL 安装

关于 WSL 的安装,相关的文章还是挺多的,这里借鉴一下已有文章 将WSL2作为生产力工具,重点关注启用并配置 WSL 2 部分即可。系统安装完后,有几个注意事项:

  • Windows Terminal 相当推荐
  • 由于一次把 VS Code 安装到非系统盘导致不能静默通过 code 命令启动在 WSL 中启动项目,这里还是建议将 VS Code 装到系统盘
  • 推荐使用 nvm 来管理 node 版本

为什么不使用 WSL 1 呢?

WSL 2 未正式发布的时候我就一直在用 WSL 1,有个让人头皮发麻的问题就是开发过程中重命名文件或者文件夹时会报错,当时我愚蠢的解决办法:删除前一个文件或者文件夹,再复制粘贴……

当得知 WSL 2 解决了这个恶心人的问题时,我毫不犹豫迁移到了 WSL 2。更多关于两者的对比,当然是看官方说明啦。

WSL 与 Windows 通信

初步打通

这是升级 WSL 一个令人窒息的问题,关于此有个相当冗长的讨论 [WSL 2] WSL 2 cannot access windows service via localhost:port,由于代理服务是运行在 Windows 上的,所以有个 WSL 使用 Windows 上服务的需求,折腾了很久才搞通了 winhost 这么一个方案,还需要配合脚本和管理员权限。

搞笑的是前段时间有个大佬分享了一个简单直接的 hostname.local:port 方案,搞得我赶紧把 winhost 这么难用的方案摒弃了。本以为一切都顺风顺水的时候,意外来了。

Windows 不可访问 WSL 部分端口

这便是今晚究级折磨我的问题!本来计划是今晚熟悉 Remix 框架,结果 Hello World 就把人尬住了:Windows 端浏览器访问页面时修改代码触发重新编译但是没有触发热重载(Live Reload)

毫无头绪的在 Remix 的 Issues 中翻找着,突然一个端口监听有误的提示让人喜出望外,赶紧看看,结果代码并没有问题,一盆冷水来得是真快,高兴还没几秒钟 _(:з」∠)_ 不过注意到了控制台提示 WebSocket 连接失败。从而衍生了另一个直击心灵的问题:为什么 Windows 端通过 3000 端口访问页面没有问题,通过 8002 端口连接 WebSocket 就挂了呢?

找了查看端口占用的资料:

  • Windows: netstat -ano | findstr ${port}
  • WSL/Ubuntu:
    • netstat -ap | grep ${port}
    • lsof -i:${port}

(flag:有空一定认真学习一个 netstatlsof 命令)

确认了在 WSL 中,3000 和 8002 端口都被占用了,但是在 Windows 只看到了 3000 被占用了。这只能说又是 WSL 的神奇机制导致的问题了。折腾来折腾去,通过以“host can access partial ports wsl2”查找,找到一个讨论 Single WSL2 localhost port can't be accessed from Windows side,一位高人让我对 WSL 有了更深入(心里无数的草泥马奔涌而过)的认识:

  • netsh int ipv4 show excludedportrange protocol=tcp 查看被 Windows 保留的端口
  • netsh int ipv4 show dynamicport tcp 查看 Windows 动态端口范围

一查才发现 8002 被 Windows 保留了,换了个没被保留的 8113 端口,页面热重载了!!!(又气又笑,“艹”,同时一铁拳直接砸到桌上)

另外,之前用 Vite 开发时通过 http://localhost:8000/ 访问不了服务,这次也发现是在 Windows 保留端口里,看起来关于 WSL 与 Windows 通信相关的问题可算是被我一个小前端摸透了?(一个猜测,不一定准

总结

当时刚听闻 WSL 时,就有个想法要好生体验一下,拿出了年轻时(高中时代)折腾安卓刷机般的干劲,碰到各种问题都是硬磕,现在 WSL 已经作为我工作和学习的核心系统,以前想要认真学习 Linux 的机会这下真来了。此外,上份工作用 MacBook 来开发,一年多了还是不习惯(用不好,特别是按键映射),对此我只能表示 Windows,YYDS!(不喜勿喷

一个值得注意的问题,此前尝试通过 WSL 1 开发 electron 应用,找到一篇文章想配置 WSL 调起可视化界面服务,折腾了很久未果,暂时搁置了。因此下一个难点或许就是 WSL 2 开发 electron 应用了?


2021-12-05 更新:

今天正打算重振旗鼓好好研究 Remix 的时候,又一个令人窒息的问题来了:3000 端口在 Windows 端访问不了了。一个简单的方法是修改 Remix 的启动端口,结果一看文档好像没有这个配置……换个思路,看来得改改 Windows 的动态端口范围了,毕竟老是让项目去适配也挺离谱的。一番查阅之后,找到了解题方法:Win10 各种端口占用问题的解决办法,使用方法二即可:

netsh int ipv4 set dynamicport tcp start=49152 num=16384

满心欢喜重启电脑之后,结果通过 localhost 访问各种端口都失败了……不得不说,太折磨了。又是一番尝试之后,还是在 Single WSL2 localhost port can't be accessed from Windows side 中找到了答案

wsl --shutdown

真是简单粗暴的做法,虽然知道这个命令,但是一心以为只是方便重启 WSL 而已。将信将疑的尝试之后,一切又恢复了往日的平静,生活又是那么美好了 _(:з」∠)_ “重启试试”这话可真不是开玩笑呢!至于原因,个人推测是因为快速启动导致 WSL 其实没有真正的重启,导致动态端口的异常?(累了,也无力深入研究了

2021-12-22 更新:

这两天又发生了一件神奇的事儿,突然之间 ping $(hostname).local 就死活 ping 不通,能看到拿到了 IP 地址,但是就是一直得不到响应。从而导致又不能通过 localhost 访问 WSL 内的服务了……甚至开始怀疑是不是这两天更新 BIOS 或者升级系统导致的问题了,几经波折,终于在两天的坚持下得到了解决……

问题的解法来自于 WSL2 unable to ping host machine,首先有个提到网络重置的办法,试过了无解。然后其中一个高赞回答提到需要自建一个防火墙入站规则,稍显麻烦,先跳过,直到看到一个提到 Virtual Machine Monitoring 规则的回答让人眼前一亮,赶紧试了一下:

image.png

马上就能 ping 通了,也能正常访问服务了,真是绝了!不过奇怪的一点是我再关闭规则好像也没什么问题了,可能是没有重启的原因?先不追究了,至少又多了一个解题思路了。现在时间 23:45:54,赶紧洗洗睡了该,毕竟 10 点才下班回来呢 _(:з」∠)_

2021-12-26 更新:

真的可怕,因为 VS Code 的更新,导致了远程服务器连接默认使用了 wslExeProxy 的连接方式,然后查看日志会发现这种方式会默认使用用户代理,然后用户代理的主机是 127.0.0.1 导致了 VS Code 通过代理都不能解析 tsconfig.json 的字段含义了……(应该是这样

然后切换回了 wsl2VMAddress 的连接方式,然后发现代理配置又不生效了……拉 github 的资源慢到爆炸。一通操作之后发现了一个神奇的现象,Windows 能流畅地访问 WSL 的各种端口服务,但是 WSL 无法访问 Windows 的端口服务,此外因为上次说的防火墙策略,ping 没有任何问题……整个人又凌乱了。

从昨天就开始排查原因,期间有通过配置防火墙规则 New-NetFirewallRule -DisplayName "WSL" -Direction Inbound -InterfaceAlias "vEthernet (WSL)" -Action Allow 成功从 WSL 通过 $(hostname).local 对应的 IP 访问到了 Windows 的服务,重启电脑一试,再也没成功过了……后来在一篇知乎文章的评论区看到说通过局域网的地址(192.168.*.* 这种)试试的时候,又可以了,重启又没成功过了……

最后通过关闭对于 vEthernet (WSL) 网络的保护立马就可以通过 $(hostname).local 访问 Windows 服务了……不过应该要注意选择合适的配置文件,更重要的一点是在电脑重启时,设置会被恢复……每次开机都自己手动配置好像有点离谱。

image.png

感觉还是不可控或者说不够了解的变量太多了,无法准确定位问题的根源。不过至此应该算是各种方法都折腾过了吧,对于 Windows 防火墙有了更进一步的了解。再看到微软官方对于 WSL 网络问题的说明时,我已经无力吐槽了,只能苦笑……

image.png

答疑解惑

如何通过改 Windows hosts 访问 WSL 上的服务?

  • WSL 中通过 ip addr | grep eth0 获取本地 IP 地址,比如得到结果 172.18.2.19
  • 配置 Hosts 172.18.2.19 test.com

值得注意的是,最开始我使用了 .app 的顶级域名,怎么都是无法访问,改成 .com 就好了,暂不清楚原因。

如何迁移 WSL?

由于默认 WSL 安装在系统盘,导致最近系统盘爆满,不得不做迁移,找到一个问答 Move WSL (Bash on Windows) root filesystem to another hard drive?,性子太急使用了手动的方式。此外如果迁移前后一样的系统名称,可通过 wsl --unregister <DistributionName> 删除之前的系统。

此外 import 安装后需要重新设置启动后的默认用户,可参考 Change default WSL 2 user after export/import 的回答。

如何让局域网内其他用户访问 WSL 上的服务?

参考 Connecting to WSL2 server via local network 讨论,首先通过:

netsh interface portproxy add v4tov4 listenport=<port-to-listen> listenaddress=0.0.0.0 connectport=<port-to-forward> connectaddress=<forward-to-this-IP-address>

使得宿主机(即 Windows)代理端口访问 WSL 上的服务,需要注意的是 connectaddress 需要配置为 WSL 的 IP,而不是 Windows 上 vEthernet (WSL) 网络的 IP。之前都是通过 localhost 访问 WSL 的服务,通过当前的配置就可以通过宿主机的本地 IP 访问 WSL 上服务了。还可以通过 netsh interface portproxy show all 查看宿主机的端口代理。

另外,如果局域网内用户还不能访问 WSL 上的服务,那就是防火墙的问题了,再通过:

netsh advfirewall firewall add rule name= "Open Port 3000" dir=in action=allow protocol=TCP localport=3000

添加对应端口的防火墙规则即可。

WSL 运行 GUI 应用

最近因为特殊需求需要使用 puppeteer 运行浏览器应用,通过研究发现官方已经做了支持,根据 Run Linux GUI apps on the Windows Subsystem for Linux 的指导,顺便升级到 Win 11 体验了一下,目前没发现什么问题, 不过现在没有主动安装文档中提到的 vGPU 的驱动,看说明是加速渲染的,已经能用了,有问题再回过头来装个驱动看看。

/usr/bin/env: ‘bash\r’: No such file or directory

第二次碰到这个问题了, 重新搜索了一下解决办法,记得之前是通过关掉 WSL 合并 Windows 的 PATH 解决的,好像会导致有一些其他小问题。这次发现有通过 wsl --shutdown 解决的,试了一下确实 OK 了,不过在此之前我通过 dos2unix 处理了一下 .zshrc 配置,不确定是不是有所帮助,因为之前通过 Windows 浏览器复制了一些东西到 .zshrc 配置中,有待研究。

其他问题汇总