点一下"转到定义",等三秒。打开个文件,状态栏转圈半分钟。想用 clangd 让跳转又快又准,结果它压根启动不了------因为你的 VS Code 在 Windows 上跑,代码却在网络盘的另一头。
这是我接手一台无外网 Linux 编译服务器时遇到的真实情况。解决办法大家都说得上来:上 Remote-SSH。可真到装的时候,网上一堆教程照着做,连不上。卡在 "Setting up SSH Host..." 转半天,最后报个错让你怀疑人生。
折腾一圈才发现,新版 VS Code 离线安装要下两个包,不是一个。网上老教程只讲一个,对新版根本无效。这篇文章就把这台离线服务器从"卡到不能用"到"clangd 秒跳"的完整过程拆开讲清楚,包括为什么这么做、踩了哪些坑、怎么排查。
你的代码跳转为什么慢到怀疑人生
先看问题出在哪。我们这台 Linux 编译服务器没外网,但 Windows 和它之间通过 SMB 把 Linux 的一个目录映射成了 F 盘。于是 VS Code 直接打开 F 盘里的工程,看起来跟本地一样。

问题在于,VS Code 的 C/C++ 扩展是作为 Windows 进程在跑的。它要读的那几千个源文件、头文件,全在 F 盘后面那台 Linux 上。每一次文件读取,都是一次跨网络的 IO。
老的 Tag Parser 方案更惨。它要扫描工程里所有文件建符号索引,几千个文件一个个读,每个都走网络。你点个跳转,它在后台疯狂读网络盘,延迟就这么累积起来的。
clangd 呢,更直接。它根本跑不起来。clangd 是个 Linux 二进制,需要 Linux 路径、Linux 工具链。你在 Windows 端装 clangd 扩展,它找不到对应的二进制,或者拿到了也跑不了。
这里有个判断基线,我后面会反复强调:做代码索引的程序,必须和被索引的文件在同一台机器上。跨网络盘做索引,注定慢,注定别扭。
Remote-SSH 的本质:把索引进程搬过去
理解了上面这点,Remote-SSH 的解法就很自然:把索引进程搬过去,代码不用动。
Remote-SSH 不是远程桌面那种"把整个界面投过来"。它把活儿拆开:UI 留在你的 Windows 上(你熟悉的快捷键和插件都在),真正干活的进程跑在 Linux 服务器上。这个干活的进程叫 vscode-server。
连上之后,clangd 扩展装在远端,作为 Linux 进程跑,直接读本地磁盘上的源文件、compile_commands.json、头文件,零网络 IO。你点跳转,clangd 在本地几毫秒就响应了,结果传回 Windows 端渲染。
为什么这比 Tag Parser 强?Tag Parser 是全盘正则扫描,扫一遍建个静态索引,慢且不准(宏、条件编译搞不定)。clangd 基于 compile_commands.json,每个文件用真实的编译参数去解析,宏展开、头文件路径都是准的。一个是从目录里翻,一个是照着编译命令精确读,根本不是一个量级。
所以 Remote-SSH 不是打补丁,它从根上纠正了"索引进程和文件分离"这个错误架构。
离线安装最大的坑:新版要两个包
到装的时候了。有外网的话啥都好说,VS Code 首次连接会自动下载 server。可我们这台没外网,自动下载必失败,卡在 "Setting up SSH Host..." 直到超时。
离线安装的思路是:在 Windows 下载好包,传到 Linux,手动解压到位。但新版 VS Code(1.102+)需要两个包,缺一不可。这是整件事最容易踩的坑。

两个包分别是:
- server 包
vscode-server-linux-x64.tar.gz,解压到~/.vscode-server/cli/servers/Stable-<commit>/server/,里面是 server 本体(node + out/ + 扩展)。 - CLI 包
vscode-cli-linux-x64.tar.gz,解压成~/.vscode-server/code-<commit>,是连接入口二进制。
为什么是两个?新版把连接管理和语言服务解耦了。ssh 进来先执行的是 CLI 入口(code-<commit>),CLI 再去拉起 server(cli/servers/Stable-<commit>/server/bin/code-server)。CLI 管连接,server 管干活。
这里还有个路径演进的坑。旧版 VS Code(大约 1.86 之前)server 装在 ~/.vscode-server/bin/<commit>/,新版改到了 ~/.vscode-server/cli/servers/Stable-<commit>/server/。网上很多老教程写的是 bin/<commit>/,对新版完全无效 。判断你装的是新版还是旧版路径:看 ~/.vscode-server/ 下有没有 code-<commit> 这个文件,有就是新版路径。
只装 server 不装 CLI 会怎样?ssh 进来找不到 code-<commit> 入口,VS Code 尝试自动下载 CLI,但没外网,连接失败。我就这么卡过,对比一位同事的 ps -ef | grep vscode-server 进程才发现,人家有 code-<commit> 入口而我没有。
commit 号:不是版本号,且必须三处一致
下载那两个包,要用到 commit 号。注意,commit 号不是版本号。
版本号是 1.102.1 这种,commit 号是一串 40 位十六进制 hash,在 VS Code 的 Help → About 里能看到,类似 7adae6a56e34cb64d08899664b814cf620465925。下载地址是拿 commit 号拼的:
https://update.code.visualstudio.com/commit:<commit号>/server-linux-x64/stable
https://update.code.visualstudio.com/commit:<commit号>/cli-linux-x64/stable
关键是,三处 commit 必须完全一致:Windows 客户端的 commit、server 包的 commit、CLI 包的 commit。差一个字符,VS Code 都认为你没装,重新触发下载,离线环境下就是连不上。
这意味着每次 VS Code 升级,commit 号会变,两个包都要重新下载对应版本。这是离线维护的持续成本,躲不掉。所以装之前一定先查准 commit,别下错版本白忙活。
完整安装十步走
理清原理后,操作其实不复杂,十步:
- Windows 装 Remote-SSH 扩展(
ms-vscode-remote.remote-ssh) Help → About查 commit 号(40 位)- 浏览器下两个包,分别重命名为
vscode-server-linux-x64.tar.gz和vscode-cli-linux-x64.tar.gz - 通过 F 盘(或任何内网传输)把两个包传到 Linux
- Linux 解压两个包到位:server 到
cli/servers/Stable-<commit>/server/,CLI 到~/.vscode-server/code-<commit> - 配 SSH config 并连接
- 装 clangd 扩展到远端
- 验证代码跳转
- 多工程共存
- 编译之外的代码怎么看
第 5 步是重点。解压用两个脚本:setup_vscode_server.sh <commit号> 把 server 包解压到新版路径,验证 node、bin/code-server、out/server-main.js 齐全;setup_vscode_cli.sh 把 CLI 包解压出 code 二进制,复制成 ~/.vscode-server/code-<commit>。
两个脚本跑完,再跑自检 check_vscode_server.sh,8 项全 [OK] 才能去连:server 的 node、server-main.js、CLI 入口、clangd、.clangd、settings.json、compile_commands.json、SSH 服务。哪项 FAIL 脚本会直接给修复命令。
SSH key 免密的四个坑
配 SSH 连接本身不难,但免密登录这里坑特别多,一个个说。
坑一:VS Code 走 ssh.exe 是非交互的。 你用 SecureCRT 连服务器,没配 key 它会弹窗问你密码。VS Code Remote-SSH 调的是 Windows 自带的 ssh.exe,非交互模式,没配 key 直接 Permission denied,不会弹窗。所以你以为"密码能连啊",到 VS Code 这就连不上。

坑二:配了 key 还得加两行配置。 生成密钥 ssh-keygen -t ed25519,把公钥传到服务器的 ~/.ssh/authorized_keys,这都好理解。但光这样不够,VS Code 还会要密码。得在 SSH config 里加:
Host nordic-server
HostName <服务器IP>
User <你的用户名>
Port 22
IdentityFile C:\Users\<你的用户名>\.ssh\id_ed25519
IdentitiesOnly yes
IdentitiesOnly yes 强制只用你指定的这个 key,避免 ssh-agent 里的其他 key 干扰。VS Code 走 ssh.exe 非交互,没这两行会回退到要密码。
坑三:key 认证对权限敏感到变态。 服务器端 ~/.ssh 必须 700、authorized_keys 必须 600、home 目录不能让 group/other 可写。权限不对,SSH 会静默拒绝你的 key,直接回退到密码认证,你看着像"key 没生效",其实是权限问题。
坑四:验证免密必须用 config 别名。 这是最隐蔽的坑。你想测免密通没通,习惯性敲 ssh <你的用户名>@<服务器IP>,发现不问密码,以为成功了。但 VS Code 还是连不上。
原因是直连 IP 时,ssh-agent 可能记住了你私钥的 passphrase,帮你免密了。但 VS Code 走的是 config 别名 + IdentitiesOnly,不走 agent,passphrase 问题就暴露了。只有 ssh nordic-server(用别名)不问任何东西,才算真免密。如果别名还问 passphrase,去掉它:
ssh-keygen -p -f $env:USERPROFILE\.ssh\id_ed25519
输旧 passphrase,新 passphrase 两次回车留空。
clangd 装远端,别装本地
连上 SSH 后,装 clangd 扩展。这里容易错------clangd 必须装在 SSH 远端,不是本地。
VS Code 的扩展面板分两栏:LOCAL(本地 Windows)和 SSH: nordic-server(远端 Linux)。在 clangd 扩展卡片上要点 "Install in SSH: nordic-server"。如果只装到 LOCAL,clangd 作为 Windows 进程跑,又回到网络盘问题了。

clangd 精确跳转的基石是 compile_commands.json。这个文件记录了每个源文件真实的编译参数,包含路径、宏定义全在里面。clangd 拿到它,每个文件都按真实编译命令解析,宏和头文件路径都是准的。没有它,clangd 只能 fallback 用默认参数猜,简单符号能跳,复杂的一跳一个不准。
这里还有个版本坑。我在 settings.json 的 clangd.arguments 里配了 --cache-dir=...,结果 clangd 启动直接退出,Output 里报 Server process exited with code 1。折腾半天才发现,clangd 18 根本没有 --cache-dir 这个参数 (--background-index-cache-dir 也没有)。手动跑 /usr/bin/clangd <参数> --check=xxx.c,stderr 会打印 Unknown command line argument '--cache-dir=...'。删掉就好了,--background-index 自带索引持久化,clangd 自己管存储位置。
教训:clangd 不同版本支持的参数不一样,配 clangd.arguments 前用 clangd --help 确认参数存在。远端装的 clangd 扩展版本(比如 0.6.0)倒不影响跳转能力,它只是个前端,真正干活的是 /usr/bin/clangd 那个二进制。
如果远端装 clangd 扩展也因网络失败,走 VSIX 离线:Windows 浏览器从扩展市场下载 .vsix,传到 Linux,VS Code 里 Install from VSIX。
连不上?按这五层逐层排查
装完连不上,别盲目重试。按这个顺序逐层定位,每层通了再查下一层:

第 1 层 网络层 :Windows PowerShell 跑 ssh <你的用户名>@<服务器IP> "echo OK"。打印 OK 就通,进下一层。超时是网络/防火墙,拒绝是 SSH 服务没开。
第 2 层 SSH 认证层 :跑 ssh nordic-server "echo OK"(用别名)。打印 OK 进下一层;Permission denied 是认证问题,回去查 key;Could not resolve hostname 是 config 没配对。注意区分:ssh user@IP 能连但 ssh nordic-server 不行是 config 问题,两个都不行是认证/网络问题。
第 3 层 server 安装层 :Linux 端跑 check_vscode_server.sh,应全 [OK]。最常见 FAIL 是 CLI 入口那项,说明没装 CLI 包。手动验证 server 能否启动:~/.vscode-server/cli/servers/Stable-$COMMIT/server/node ~/.vscode-server/cli/servers/Stable-$COMMIT/server/out/server-main.js --version,打印版本号就正常。再确认三处 commit 完全一致。
第 4 层 VS Code 状态层 :前三层都通还连不上,通常是 VS Code 缓存了失败状态。F1 → Remote-SSH: Kill VS Code Server on Host,再 Developer: Reload Window,重新连。远端可清残留:pkill -f vscode-server、清 logs 和 lock 文件,但别删 cli/servers/ 和 code-<commit>,那是安装包。
第 5 层 clangd 层 :连上了但跳转不对,看这层。打开 .c 文件,View → Output 选 clangd 通道,看日志。clangd version 18.1.3 + Loaded compilation database from ... 就是正常的。
有个实战定位手段很管用:如果同事用同版本 VS Code 在同台服务器连上了,对比他的 ~/.vscode-server/ 路径结构,ps -ef | grep vscode-server 看他的进程命令行,照抄路径。我这次排查就是靠对比同事进程发现新版路径变了、需要 CLI 包的。
编译之外的代码怎么看
clangd 有个局限得说清楚:只有编译进镜像的文件才有精确跳转。没在 compile_commands.json 里的文件,clangd 没有编译参数,跳转会失败或不准。
哪些算"编译之外"?prj.conf 里 CONFIG_XXX=n 的源文件、其他工程的代码、SDK 里没选用的驱动、samples 和 tests 目录。这些 clangd 都管不了。
对策按推荐顺序:第一,用 CodeGraph(如果有的话),它索引整个工作区所有符号,不依赖 compile_commands,没编译的代码也能查,几秒出结果,补上 clangd 的短板。第二,VS Code 全局文本搜索 Ctrl+Shift+F 兜底,不精确(同名混),但能定位文件再人工判断。第三,把要看的文件纳入编译------Zephyr/NCS 在 prj.conf 开 CONFIG_XXX=y 重新 build,CMake 在 CMakeLists 加源文件重新 cmake,一劳永逸。第四,少数文件用 compile_flags.txt,每行一个参数放文件所在目录。第五,clangd fallback 模式自动启用但别指望它准。
日常跳转用 clangd,编译内的秒跳。跨工程或编译外的代码,用 CodeGraph 或全局搜索。长期要看的模块,开配置重新 build 纳入 clangd。
六条真实踩坑记录
这次安装实际遇到的问题,供你排查时参考:
-
只装 server 没装 CLI → 连接失败。根因:新版需要
cli-linux-x64单独的包作 ssh 入口。定位:对比同事进程发现他有code-<commit>而我没有。 -
server 装在旧路径
bin/<commit>/→ VS Code 找不到。根因:新版路径改到cli/servers/Stable-<commit>/server/。定位:看同事进程命令行里的路径照抄。 -
server 入口文件名变了 → 老脚本找
bin/code-server-oss找不到。根因:新版叫bin/code-server(无 -oss)。不影响 VS Code(它按 product.json 找),但自检脚本要适配。 -
SecureCRT 能连但 VS Code 不能 → 排除网络/认证,锁定 VS Code 自身。定位:PowerShell 跑
ssh <你的用户名>@<服务器IP> "echo OK"直接打印 OK,说明 Windows 的 ssh.exe 没问题,问题在 server 安装层。 -
本机自连报 Permission denied → 误判为认证问题。实际是本机没密码,不代表服务器拒绝你。教训:诊断命令要在客户端(Windows)跑,不是在服务器本机跑。
-
clangd
Server process exited with code 1→ clangd 启动失败。根因:clangd.arguments配了--cache-dir,但 clangd 18 没这参数,启动直接退出。定位:手动跑/usr/bin/clangd <参数> --check=xxx.c,stderr 打印Unknown command line argument。解决:删掉--cache-dir。
升级维护
VS Code 升级后 commit 号变,需重新走下载两包、解压的流程。旧 commit 目录可删:rm -rf ~/.vscode-server/cli/servers/Stable-<旧commit> 和 rm -f ~/.vscode-server/code-<旧commit>。
改了 prj.conf 后跳转变不准?重新 build 生成新的 compile_commands.json,clangd 自动重载(.clangd 里配了 Index.Background: Build)。
回到开头那个判断基线:索引进程必须和文件同机。Remote-SSH 就是把这条原则在离线环境里落地。离线环境下真正的难点是新版那两个包、commit 三处一致、SSH key 的四个坑。这些理清了,clangd 秒跳不难。
你离线服务器上还在用网络盘硬扛代码跳转吗?或者装 Remote-SSH 卡在哪一步了?评论区说说,有用的话点个在看,让更多被网络盘折磨的工程师看到。
标签:VS Code · Remote-SSH · 离线安装 · clangd · 嵌入式开发