【免费下载链接】momo is a Markdown viewer that opens .md files in a browser.项目地址https://gitcode.com/gh_mirrors/mo7/mo点击查看免费下载mo 是一款把 Markdown 文件直接在浏览器中打开的 Markdown viewer。它的核心设计是单实例默认只在6275端口运行一个服务器进程第二次运行 mo 时不会启动新服务器而是把新文件追加到已存在的会话里。本文将带你读懂这套单服务器、多文件Single Server, Multiple Files机制的实现思路mo 如何探测到已有服务、如何通过 HTTP 接口追加文件、两个 mo 同时启动时如何避免冲突以及会话备份如何让你在重启后不丢文件。一、先看懂场景连续两次运行 mo 会发生什么假设你已经安装好 mo在项目里执行$ mo README.md # 第一次后台启动 mo 服务器并打开浏览器 mo: serving at http://localhost:6275 (pid 12345) $ mo CHANGELOG.md # 第二次不会新开服务器而是把文件加进去 mo: added 1 item(s) to http://localhost:6275第二次运行 mo 时浏览器里会多出一个CHANGELOG.md的侧边栏条目而终端立刻返回、不会阻塞。这正是 mo 官方帮助里明确描述的Single Server, Multiple Files行为如果相同端口上已有 mo 服务器在运行后续的 mo 调用会把文件加入现有会话而不是启动一个新的。这种设计对新手非常友好浏览器入口唯一不管打开多少个文件都集中在http://localhost:6275一个地址不用来回切换标签页里多个不同端口的服务会话天然累积写文档、写笔记时可以随手mo xxx.md所有文件自动汇总到同一侧边栏配合会话恢复服务器重启后之前打开的文件列表会自动恢复下文详述 如果你确实想要一个完全独立的会话只需要换一个端口mo draft.md -p 6276。二、追加文件是如何实现的探测 HTTP 接口mo 的命令行部分本质上是一个客户端它先探测 6275 端口上有没有一个 mo 服务器在跑再决定追加还是新建。核心逻辑集中在 cmd/root.go 的run函数中。第一步用健康检查接口探测服务器是否存活mo 通过 probeServer 向GET /_/api/status发起一次带超时的 HTTP 请求并校验响应 JSON 中是否包含version字段——这样即使端口被别的程序占用也不会误认为是 mo 实例。注意两个不同的超时设置见 cmd/root.go#L40-L52场景超时含义probeTimeoutFast500ms没服务器是常态比如第一次启动快速失败probeTimeoutDefault2s预期服务器应该在跑比如mo不带参数时只打开浏览器第二步服务器活着 → 通过 API 逐个追加文件探测成功后mo 把每个文件以POST /_/api/groups/{group}/files的形式发给服务器实现见 postFiles服务器端在内存状态里登记该文件并开始监听它的变化live-reload完整的路由表可以在 internal/server/server.go#L1640-L1655 看到。服务器端 State.AddFile 还有一个细节如果文件已经存在于同组中会直接返回已有条目天然幂等——你重复执行mo README.md不会造成重复条目。第三步服务器没活着 → 派生一个后台子进程探测失败时mo 走 startBackground 路径把要打开的文件清单写成一个临时 JSON 恢复文件WriteRestoreFile用--foreground --no-open --restore参数把自己再次执行一次spawnNewProcess子进程成为真正的常驻服务器父进程立刻退出并把子进程 detach 掉所以终端不会被占住随后用 waitForReady 轮询确认服务器就绪再打开浏览器也就是说常驻服务器其实是由父进程临时拉起的前台子进程这样既方便调试也让--restart这类操作可以优雅地用旧进程退出 新进程继承的方式完成。三、两个 mo 同时启动怎么办靠 PID 校验防双开如果你极快连按两次回车或者两个终端同时执行mo两个父进程可能都探测不到服务器于是各自派生子进程去监听 6275 端口——此时只有一个人的net.Listen能成功另一个子进程会因端口冲突而退出。mo 用一套精巧的判定来兜底waitForReady 的注释直接点明了这一点轮询/_/api/status时把响应里报告的 PID与自己派生的子进程 PID比对PID 一致 → 服务器是自己拉起来的正常继续PID 不一致 → 说明端口被另一个 mo 实例抢走了返回errServerConflict随后走 addToRunningServer不报错而是把自己要开的文件追加给获胜的那个服务器mo: another mo server is already running at http://localhost:6275 (pid 999); added 1 item(s) to it另外如果子进程死掉后 1 秒内deadChildGrace端口上出现了新的 mo 服务也会按并发竞争输家处理而不是直接报错。这套输了就加入的策略保证了无论时序如何交错结果总是唯一的服务器 完整的文件列表这正是单实例设计的可靠性关键。四、会话备份与恢复关掉 mo 再打开文件不丢单实例服务器跑在内存里进程一停状态就没了。mo 的解法是自动会话备份internal/backup/backup.go每次文件增删改都会标记状态脏服务器内部用 1 秒防抖后把全部组、文件路径、watch 模式和上传内容快照到XDG_STATE_HOME/mo/backup/mo-6275.json防抖循环见 State.EnableBackup下次新服务器启动时mo 会Load这份备份用 mergeGroups 把旧会话与新命令行参数合并旧文件在前、新文件在后并跳过已经不存在于磁盘上的文件filterValidRestoreData$ mo README.md CHANGELOG.md # 启动两个文件 $ mo --shutdown # 关闭服务器 $ mo # 自动恢复 README.md 和 CHANGELOG.md $ mo TODO.md # 恢复旧会话 追加 TODO.md配合--target-t分组你可以在同一个单实例服务器里维护多个互不干扰的工作区每个组有独立 URL 和侧边栏例如mo spec.md -t design打开http://localhost:6275/design日常运维也只需三个命令mo --status查看运行中的服务器、mo --shutdown停止、mo --restart保留会话重启实现见 doShutdown / doRestart。日志统一落在XDG_STATE_HOME/mo/log/mo-6275.loginternal/logfile/logfile.go--status就是靠扫描这个日志目录里的mo-{端口}.log来发现所有端口上的实例的discoverPorts。五、核心文件导读想深入源码从哪里看模块路径说明命令入口与单实例调度cmd/root.go探测 → 追加 / 新建的完整决策流服务器探测cmd/root.goprobeServer健康检查与 mo 实例校验后台派生与就绪等待cmd/root.gostartBackground与 PID 竞争判定会话备份读写internal/backup/backup.go原子写入临时文件 rename的备份保存内存状态与备份循环internal/server/server.goRestoreData快照与 1 秒防抖保存API 路由表internal/server/server.go追加文件、分组、shutdown 等全部端点日志与端口发现internal/logfile/logfile.go按端口命名的轮转日志目录六、小结单实例设计的三个关键点一个端口一个会话mo 把6275 端口当作会话的唯一标识第二次运行先探测、后追加浏览器入口始终统一并发不冲突PID 校验 输了就加入addToRunningServer让同时启动的多个 mo 自动收敛为一个服务器状态可恢复后台防抖备份 启动时合并恢复让单实例 内存态的服务器依然拥有持久化的用户体验。理解了这套机制你就能自如地在 mo 里追加文件、用-t分组、用-p开独立会话、用--status / --shutdown / --restart管理后台服务器——这正是 README.md 中Single server, multiple files一节想传达的全部。赞分享【免费下载链接】momo is a Markdown viewer that opens .md files in a browser.项目地址https://gitcode.com/gh_mirrors/mo7/mo点击查看免费下载相关推荐Streamyfin 首次启动介绍弹窗Intro Modal触发位置设计为什么必须放在 Home.tsx 而不是 Tabs 布局中Streamyfin 首次启动介绍弹窗Intro Modal触发位置设计为什么必须放在 Home.tsx 而不是 Tabs 布局中 导读 Streamyf音视频移动开发行为测试指南用 Quick 验证“做什么”而不是“怎么做”行为测试指南用 Quick 验证“做什么”而不是“怎么做” 测试只有在应用程序 行为与预期不符 时才应该失败。测试应当验证应用代码 做了什么what 而测试开发工具FilePond技术栈解析为什么选择Rollup而不是WebpackFilePond技术栈解析为什么选择Rollup而不是Webpack 在现代前端工程化体系中构建工具的选择直接影响项目的开发效率和产物质量。FilePond前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考