65.9K
CodeProject 正在变化。 阅读更多。
Home

TorpedoSync - 基于局域网的机器间文件同步

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (36投票s)

2018年1月17日

CPOL

20分钟阅读

viewsIcon

62430

downloadIcon

3070

TorpedoSync 的诞生源于我使用其他软件在机器间同步“我的文档”时遇到的麻烦。

 

介绍

他们说“需求是发明之母”,嗯,需求,有时也有一点自己动手做的冲动。这就是 TorpedoSync 的诞生方式,源于我使用其他软件在我的机器之间同步“我的文档”,并发现它们令人烦恼。因此,发出了不朽的呐喊:“我可以做得更好!”。

我最初使用 bitsync,后来变成了 resilio sync(一个免费增值产品),我一开始很喜欢它,但后来它变得非常占用内存和磁盘空间。

经过大量搜索,我找到了 syncthing,它是开源的,用 go 编写,我发现它似乎连接到互联网,并且不如 resilio 好用。

此时我放弃了,决定自己编写。

源代码也可以在 github 上找到:https://github.com/mgholam/TorpedoSync

主要功能

TorpedoSync 具有以下功能:

  • 在本地网络中的计算机之间同步文件夹
  • 默认安全:更改和删除的文件首先会被复制到“archive”文件夹
  • 基于 .net 4:因此它可以在旧的 Windows XP 计算机上运行,非常适合备份机器。
  • 内置 Web UI
  • 作为控制台应用程序或服务运行
  • 单个小型 EXE 文件用于复制部署
  • 无需数据库
  • 最小内存使用
  • 能够压缩复制小文件:为了更好的网络往返性能(同步诸如 node_modules 等麻烦的文件夹)
  • 只读共享:从主服务器到客户端的单向数据同步
  • 读写共享:双向同步
  • 可忽略的文件和文件夹
  • 支持树莓派
  • 支持 Linux

安装

要运行 TorpedoSync,只需执行文件,它就会启动并在您的浏览器中显示 Web UI。

或者,您可以通过 torpedosync.exe /iTorpedoSync 安装为 Windows 服务,并使用 torpedosync.exe /u 卸载它。

使用 TorpedoSync

x

在您要共享文件夹的机器上,单击“Shares”(共享)选项卡下的“Add”(添加)按钮。

x

输入所需的值,并可选择复制您希望其他机器连接的令牌之一。

在客户端机器上,打开“Connections”(连接)选项卡并单击“Connect to”(连接到)按钮。

x

输入所需的值并保存,客户端将等待主服务器的确认。

x

单击“Connections”(连接)列表下客户端的机器名称旁边的“Confirm”(确认)按钮,之后客户端将开始同步。

要查看进度,您可以单击连接共享名称以查看连接信息,或者您可以切换到“Logs”(日志)选项卡。

x

x

您可以通过“Settings”(设置)选项卡查看和更改 TorpedoSync 配置。

x

与竞争对手的区别

Torpedosyncresiliosyncthing 等其他文件同步应用程序采取了不同的方法,因为它不为每个文件计算加密哈希值,因此速度更快,使用的 CPU 周期更少。

没有使用关系数据库来存储文件信息,因此性能更高,存储空间更小。

TorpedoSync 只在本地网络环境中使用,不通过互联网同步,因此您可能更安全,并且您的数据仅限于您自己的网络。

定义

要理解 TorpedoSync 中实现的功能,您必须熟悉以下定义:

  • 共享:您要共享的文件夹。
  • 状态:共享的文件和文件夹列表。
  • 增量:比较状态的结果,您将获得一个已添加、已删除、已更改的文件和已删除的文件夹列表。
  • 队列:要下载的文件的列表。
  • Connection
    • 主机器:定义共享的计算机,即使在读写模式下也发生增量计算。
    • 客户端机器:连接到主服务器并启动同步的计算机。

同步规则

  • 主设备是定义“共享”的机器。
  • 默认情况下,将较旧或已删除的文件移动到“old”文件夹进行备份。
  • 读写模式下的首次同步将所有文件复制到两端。
  • 即使在读写模式下,也只有客户端发起同步。
  • 增量处理在主设备上完成。
  • 如果 OLDER 或 LOCKED -> 放入错误队列并跳过,以便稍后在另一次同步中重试。
  • 如果 NOTFOUND -> 放入错误队列。

工作原理

要使同步正常工作,连接两端的机器应大致时间同步,尤其是在执行双向或读写同步时。

在您自己的机器之间同步我的文档的情况下,这并不重要,因为您不会在两台机器上同时更改文件。如果确实发生这种情况,您可以从共享目录中的 .ts\old 文件夹中恢复文件。

如前所述,您有两种同步类型:只读和读写。

只读/单向同步

客户端在此模式下执行以下操作:

  1. 为文件夹生成一个 State,即获取共享的文件和文件夹列表。
  2. 将此 State 发送到服务器,并等待服务器返回一个 Delta
  3. 根据 Delta 中删除的文件和文件夹,将其移动到 .ts\old
  4. 根据 Delta 中添加和更改的文件,将其排队等待下载。

服务器在此模式下执行以下操作:

  1. 等待客户端发送 State
  2. 获取共享的当前 State
  3. 根据上述两个状态计算客户端的 Delta 并发送给客户端。

读写/双向同步

客户端在此模式下执行以下操作:

  1. 为文件夹生成一个 State,即获取共享的文件和文件夹列表。
  2. 根据上述状态和上次存储的 State(如果存在)创建 Delta
  3. 清除上述 Delta 中已添加和已更改的文件。
  4. 将当前的 StateDelta 发送给服务器,并等待服务器返回一个 Delta
  5. 根据 Delta 中删除的文件和文件夹,将其移动到 .ts\old
  6. 根据 Delta 中添加和更改的文件,将其排队等待下载。

服务器在此模式下执行以下操作:

  1. 删除客户端发送的 Delta 中定义的文件。
  2. 比较主服务器上当前的 State 和上次保存的 State,为客户端创建已删除文件和文件夹的 Delta
  3. 使用主服务器的 State 处理客户端的 State,以获取主服务器已更改和已添加的 Delta
  4. 使用客户端的 State 处理主服务器的 State,以获取客户端已更改和已添加的 Delta
  5. 将客户端的 Delta 返回给客户端。
  6. 在主服务器上将主服务器的 Delta 排队等待下载。

下载文件

一旦生成 Delta,其中定义的已删除文件和文件夹将以原始文件的确切文件夹结构移动到 .ts\old 下,而添加和更改的文件则排队等待下载。

下载器以两种方式处理队列:

  1. 如果队列中的文件小于 200kb(一个配置)且这些文件的总大小小于 100mb(另一个配置),它们将被分组并从主服务器下载为 zip 文件,这旨在提高小文件的往返性能。
  2. 如果文件大于 200kb,则单独下载并分成 10mb 的块。

下载块大小可配置,应反映网络连接的带宽,例如,对于有线网络,10mb 应该没问题,对于无线网络,1mb 会更好(每秒 1 块)。

下载结果可以是以下之一:

  • Ok:数据正常并已写入磁盘。
  • 未找到:请求的文件在主服务器上未找到,并被忽略。
  • 旧版本:主服务器上的文件比请求的旧,请求被放入错误队列。
  • 锁定:主服务器上的文件被锁定,无法下载,请求被放入错误队列。

错误队列不过是失败文件的容器并会重置,因为相关文件无论如何都可能会在下次同步中出现。

内部结构

源代码由以下部分组成:

描述
TorpedoSyncServer 加载所有内容并处理 TCP 请求的主类
TorpedoWeb 派生自 CoreWebServer 的 Web API 处理程序
ServerCommands 用于服务器端处理 TCP 请求(如同步和 zip 创建等)的静态类
ClientCommands 用于发送 TCP 客户端请求的静态类
CoreWebServer 基于 HttpListener 设计为抽象类的 Web 服务器代码
DeltaProcessor 用于状态和增量处理及计算的静态类
QueueProcessor 处理共享连接、排队下载、下载文件和新同步初始化的主类
UDP 包含维护当前连接机器地址的 UDP 网络发现代码
NetworkClient, NetworkServer 为降低内存使用而修改的 RaptorDB TCP 代码
全局变量 全局配置设置

文件和文件夹

一旦在主设备上创建了 ShareTorpedoSync 就会在该共享文件夹中创建一个名为 .ts 的文件夹,用于存储名为 old 的“旧存档”文件。此外,在 .ts 文件夹中还有一个名为 .ignore 的文件,您可以使用它来定义在同步过程中要忽略的文件和文件夹(即不会被传输)。

除了 EXE 文件,TorpedoSync 还会创建一个 log 文件夹用于日志,以及一个 Computers 文件夹用于连接信息。在 Computers 文件夹内部,会为每台连接的机器创建一个文件夹,其名称为机器名,用于存储该计算机连接到的每个共享的状态、配置和下载队列。

.ignore 文件

在共享文件夹内的特殊 .ts 目录中,您有一个特殊文件,其中包含您在同步时希望忽略的文件和文件夹列表,每行是一个忽略项,遵循以下规则:

  • # 之后的所有内容都是注释
  • * 或 ? 在行中表示通配符,将用于匹配文件名
  • \ 在行中表示目录分隔符
  • 其他任何内容都将匹配路径

例如

# comment line
obj            # any files and folder under 'obj'
*.suo          # any file ending in .suo
code\cache     # any files and folder inside 'code\cache'

settings.config 文件

settings.config 文件位于 TorpedoSync EXE 文件旁边,并以 json 格式存储以下配置参数:

参数 描述
UDPPort 用于网络发现的 UDP 端口(默认 21000)
TCPPort 用于同步的 TCP 端口(默认 21001)
WebPort Web UI 端口(默认 88)
LocalOnlyWeb Web UI 只能在同一台机器上访问(默认 true)
DownloadBlockSizeMB 下载文件的分块大小,即,如果您在无线网络上,将其设置为 1 以匹配介质的传输速率(默认 10)
BatchZip 批量压缩下载文件(默认 true)
BatchZipFilesUnderMB 如果总大小低于此值,则批量压缩下载文件(默认 100)
StartWebUI 如果从控制台运行,则启动 Web UI(默认 true)

sharename.config 文件

此文件以 JSON 格式存储共享连接的配置。

参数 描述
名称 共享名称
Path 共享文件夹的本地路径
MachineName 要连接的机器名称
Token 共享令牌
isConfirmed 在主服务器上,此值表示共享已确认并准备好发送数据
ReadOnly 表示此共享是只读的
isPaused 此共享连接是否已暂停
isClient 如果此共享连接是客户端

sharename.state 文件

在双向读写模式下,此文件存储上一个文件和文件夹列表,TorpedoSync 从中推断自上次同步过程以来哪些文件和文件夹已被删除。

如果此文件不存在(即首次同步),则 TorpedoSync 假定这是首次同步,并且所有丢失的文件都将传输到连接的两端(连接两端不会删除任何丢失的文件和文件夹)。

此文件采用二进制 json 格式。

sharename.queue 和 .queueerror 文件

这些文件存储应从另一台机器下载的文件,以及下载失败的文件列表。

这些文件采用二进制 JSON 格式。

sharename.info 文件

这些文件存储共享统计数据和度量信息,采用二进制 JSON 格式。

开发过程

TorpedoSync 重用了 RaptorDB 的一些代码,我最初包含了 raptordb.common.dll,但后来决定将代码复制到解决方案中,这样我就只有一个 EXE 文件可以部署,这让用户更容易。

该项目分为两部分:

  • 主要的 c# 代码
  • Web UI,它构建到主代码文件夹中的一个文件夹中

Web UI 使用 vue.js 构建。

因此,您必须具备以下条件才能编译源代码:

  • Visual Studio 2017 (我使用社区版)
  • Visual Studio Code
    • Vetur 扩展
  • node 和 npm
    •   在 WebUI 文件夹中运行 npm install

您可以在 Visual Studio 中编译 c# 代码。

您可以通过运行 WebUI\run.cmd 调试 Web UI,这将启动一个节点开发服务器用于调试和 webpack 中的快速代码编译。

当您对 UI 代码满意后,执行以下操作:

  • WebUI\build.cmd 构建 UI 的生产版本
  • WebUI\deploy.cmd 将生产文件复制到 c# 源文件夹
  • 重建 c# 解决方案以包含 UI 代码

痛点

以下是编写此项目时遇到的一些痛点。

长文件名

虽然 Windows 支持长文件名(>260 个字符),但 .net 在这方面似乎有所欠缺(尤其是 .net 4)。这些文件特别是在处理 node_modules 文件夹时出现,这些文件夹嵌套很深,经常超过 260 个字符的限制。

为了处理这个问题,有特殊的 longfile.cslongdirectory.cs 源文件,它们直接使用底层操作系统。

我遇到的另一个痛点是,即使 Path.GetDirectoryName() 也受此影响,所以我不得不编写一个字符串解析器来处理它。

来自多台机器的UDP响应

我最初的 UDP 代码似乎无法从网络中的所有其他机器收到响应,这花了我很长时间才弄清楚,问题在于,我关闭了 UDP 连接,并没有在流中获取所有响应。

所以解决方案是停留在 while 循环中,不仅处理第一个响应,还处理任何其他响应或超时。

zip格式中愚蠢的日期

这个问题花了我很长时间才弄清楚,问题始于双向同步下载似乎已经下载的文件,更糟糕的是似乎陷入了循环回到主服务器的情况。

经过一番冥思苦想,问题似乎在使用批量 zip 功能时出现,并且在查看 Jaime Olivares 的代码时,我在注释中发现了这个亮点:

DOS Date and time:
     MS-DOS date. The date is a packed value with the following format. Bits Description
                0-4 Day of the month (1..31)
                5-8 Month (1 = January, 2 = February, and so on)
                9-15 Year offset from 1980 (add 1980 to get actual year)
     MS-DOS time. The time is a packed value with the following format. Bits Description
                0-4 Second divided by 2
                5-10 Minute (0..59)
                11-15 Hour (0..23 on a 24-hour clock)

问题在于存储时秒数被除以 2。经过大量研究,我发现这是 FAT 在 MSDOS 时代的一个限制,并在原始 zip 格式中一直延续到今天,后来对 zip 的扩展支持完整的日期时间,但不幸的是 Jaime 的代码没有这个,但它确实有每个压缩文件的注释部分,我用它来存储每个文件的日期时间,直到 Jaime 开始支持扩展 zip 头。

Webpack

我不是一个 Web 开发人员,最近才开始接触,所以弄清楚 webpack 和 npm 对我来说很棘手,特别是构建过程,它需要很长的时间。另外,为特殊功能配置 Webpack 真的很难也很令人沮丧,因为我总是从我在网上找到的片段中得到错误,所以大部分情况下我基本放弃了。

过了一段时间,我发现如果你 npm run dev 代码,你的浏览器中会获得热代码更改,开发周期会大大缩短和愉快(即你不需要在每次代码更改后构建并等待...)

npm run dev 的问题在于 Web 代码运行在端口 8080,而我的服务器运行在端口 88,所以我在 debug.js 文件中想出了以下解决方案:

import './main.js'
window.ServerURL = "https://:88/";

这会将一个变量设置到我的 C# 代码中的 Web 服务器,在生产构建中我忽略它并只打包 main.js 文件,所以 webpack.config.js 具有以下内容:

var path = require('path')
var webpack = require('webpack')

module.exports = {
    entry: './src/debug.js',    //normal dev build
...
if (process.env.NODE_ENV === 'production') {
    module.exports.entry = './src/main.js'    // if production
    module.exports.devtool = '#source-map'
...

我用这种方法遇到的唯一问题是,我无法向我的服务器 POST json,并且在浏览器中会收到 CORS(跨域资源共享)错误,我的请求在开发者模式下无法到达我的服务器。

虽然我在代码中只有一个地方发布了数据,我可以绕过它,但在一个更大的项目中这将是一个问题,所以需要一个更好的解决方案。

乐事

以下是我在这个项目中使用时感到愉快的一些东西。

Visual Studio Code

起初我使用 Visual Studio Community 2017 来编写 Web UI 代码,但我发现 vue.js 支持不足,帮助不大。于是我决定使用 Visual Studio Code,并借助一些 vue.js 扩展,不得不说它非常出色且令人愉悦。

总体可用性和生产力非常高,我绝对推荐它用于 Web 开发。

Vue.js

在 webpack 正常工作并支持编译 .vue 文件后,我不得不说 vue.js 表现出色且易于编码(.vue 文件帮助很大,简化了开发过程)。

我四处寻找“框架”,发现 vue.js 功能强大,最重要的是对于从未接触过 JavaScript 的人来说,它简单易学易用。我还研究了 angular.js,但我发现它过于复杂和困难。

附录 v1.1 - 树莓派和 Ubuntu

x

代码重构已完成,TorpedoSync 现在可以在 树莓派Ubuntu 操作系统上运行。上面的动态 GIF 是我的 pi b2+ 正在同步我的运行 Win 10 的开发机器上的数据。

要在树莓派和 Ubuntu 上运行 TorpedoSync,您必须使用以下命令安装 mono,EXE 文件与在 Windows 上运行的文件相同。

sudo apt-get install mono-complete

附录 v1.2 - npm3

我最近遇到了 npm3,这是一个更新的版本,它能够优化 web ui 项目的依赖项,这样做使我的 node_modules 文件夹大小从 76Mb 减少到大约 56Mb,节省了我 20Mb。

所以我相应地切换了构建和运行脚本(用法相同,只需在末尾添加一个 3)。

附录 v1.5.0 - Parcel 打包器

GitHub 不断发出关于 TorpedoSync Web UI 的 node 模块中某些 javascript 库存在安全漏洞的通知,我决定将 webpack 从 v3 升级到 v4。这真是一个痛苦的过程,构建脚本失败了,而修复它超出了我的能力范围,因为这些脚本大多是从各种来源拼凑而成的,一开始就超出了我的理解。

被过于复杂的 webpack 打包器弄得沮丧不堪,我四处寻找,发现了 parcel 打包器(链接),它简单且无需配置,大部分功能开箱即用。

我使用 parcel 唯一的问题是它会在输出文件名后附加一个哈希值,文档中指出这是为了用于内容分发网络,但我一直无法关闭它,而且似乎也无法关闭。

这种名称混淆在尝试将文件嵌入 TorpedoSync 时非常痛苦,因为每次更改都必须编辑项目文件。

除了上述之外,parcel 的其他一切都很棒,我非常喜欢它,因为我无需摆弄各种设置和配置。

附录 v1.6.0 - 使用 Svelte 重写 Web UI

我继续对 svelte 的热爱,从重写 RaptorDB Web UI 开始,我决定对 TorpedoSync 也这样做。虽然 vuesvelte 的结构和思维方式相似,但转换过程需要我从头创建一些 svelte 组件(我搜索时找不到)。这些组件是:

  • 可移动/可调整大小的模态表单
  • 一个用于替代 sweet alert 的消息框,我对这个消息框控件感到非常自豪,因为它小巧、强大且非常易于使用。
    • 它支持“确定”、“确定/取消”、“是/否”、单行输入、多行输入和进度指示器。

结果相当显著:

  • vue 分布式构建
    • js 大小 = 147 kb
    • 总大小 = 201 kb
  • svelte 分布式构建
    • js 大小 = 83 kb
    • 总大小 = 91 kb

一些亮点:

  • 我将图标从 glyphicons 字体更改为 svg font awesome 组件。虽然输出的总体大小更小,但如果您的应用程序中有大量图标,这并不是一个好的选择(使用的 14 个图标占用了约 13kb 的空间,而整个 glyph 字体约为 23kb)。
  • 即使包含 font awesome 图标,svelte 项目的 node_modules 大小也约为 vue 的一半。
  • rollup javascript 打包器虽然不像 parcel 那样无需配置(它有一个可管理的配置文件),但在磁盘上只有 4mb,而 parcel 几乎达到 100mb。
  • 我将散布在源文件中的 CSS 颜色更改为使用全局 CSS var(),这允许主题和即时颜色更改(目前在 UI 中禁用)。

以前的版本

您可以在此处下载以前的版本

历史

  • 初始发布:2018年1月17日
  • 更新 v1.1:2018年2月6日
    • 代码重构
    • 在树莓派和 ubuntu 上使用 mono
  • 更新 v1.2:2018年2月14日
    • bug 修复:网页刷新提取服务器地址
    • GetServerIP() 重试逻辑
    • npm3 更改了构建运行脚本
  • 更新 v1.3:2018年7月2日
    • 精简 TCP 网络代码
    • 如果主服务器返回 null,则删除重试 -> 在下一次同步中重做(边缘情况:队列卡住且无法同步)
    • 压缩时检查文件是否存在(日志中不再出现异常)
  • 更新 v1.5.0 :
    • 改用 parcel 打包器代替 webpack
    • 更新到新的 zipstorer.cs
  • 更新 v1.6.0:2019年8月20日
    • 使用 svelte 重写了 web ui
  • 更新 v1.7.0:2019年12月10日
    • WEBUI: 将选项卡从应用程序重构为自己的组件
    • 升级到 fastJSON v2.3.1
    • 升级到 fastBinaryJSON v1.6.1
    • bug 修复 zipstorer.cs 默认不是 utf8 编码
    • 添加了删除空文件夹的功能
    • 更新了 npm 包
  • 更新 v1.7.5:2020年2月18日
    • 更新了 npm 包
    • 增加了对环境变量路径变量的支持(%LocalAppData% 等)
  • 更新 v1.7.6:2020年5月23日
    • WEBUI 使用 fetch()
    • js 代码清理
    • 模态表单默认槽错误
    • 网络重试逻辑(感谢 ozz-project
© . All rights reserved.