├── .classpath ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs ├── org.eclipse.m2e.core.prefs └── org.springframework.ide.eclipse.xml.namespaces.prefs ├── README.md ├── design ├── design.pptx └── flow.png ├── logs ├── error.log ├── error.log.2020-07-10 ├── error.log.2020-07-13 ├── log.log ├── log.log.2020-07-10 └── log.log.2020-07-13 ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── laz │ │ │ └── filesync │ │ │ ├── FileSyncMain.java │ │ │ ├── client │ │ │ ├── FileSendClient.java │ │ │ ├── FileSyncClient.java │ │ │ ├── file │ │ │ │ └── handler │ │ │ │ │ └── MessageEncoder.java │ │ │ ├── handler │ │ │ │ └── MsgClientHandler.java │ │ │ └── msg │ │ │ │ ├── DiffFilesSyncMsg.java │ │ │ │ └── RequestMsg.java │ │ │ ├── conf │ │ │ └── Configuration.java │ │ │ ├── msg │ │ │ ├── BaseMsg.java │ │ │ ├── ErrorMsg.java │ │ │ └── MsgType.java │ │ │ ├── rysnc │ │ │ ├── checksums │ │ │ │ ├── BlockChecksums.java │ │ │ │ ├── DiffCheckItem.java │ │ │ │ ├── DiffFileMeta.java │ │ │ │ ├── FileChecksums.java │ │ │ │ └── RollingChecksum.java │ │ │ └── util │ │ │ │ ├── ByteTool.java │ │ │ │ ├── Constants.java │ │ │ │ ├── QuickMD5.java │ │ │ │ ├── RsyncException.java │ │ │ │ └── RsyncFileUtils.java │ │ │ ├── server │ │ │ ├── FileReceiveServer.java │ │ │ ├── FileSyncServer.java │ │ │ ├── file │ │ │ │ └── handler │ │ │ │ │ ├── FileReceiveServerHandler.java │ │ │ │ │ └── FileSendClientHandler.java │ │ │ ├── handler │ │ │ │ └── MsgServerHandler.java │ │ │ └── msg │ │ │ │ ├── FileCheckSumsMsg.java │ │ │ │ └── FileInfo.java │ │ │ └── util │ │ │ ├── Coder.java │ │ │ ├── Constants.java │ │ │ ├── FileSyncUtil.java │ │ │ ├── FileUtil.java │ │ │ ├── JsonUtil.java │ │ │ ├── PathMap.java │ │ │ └── ZipUtils.java │ └── resources │ │ ├── log4j.properties │ │ ├── small_client.txt │ │ └── small_server.txt └── test │ ├── java │ └── com │ │ └── laz │ │ └── filesync │ │ └── test │ │ └── TestSync.java │ └── resources │ ├── diff │ ├── lorem │ ├── lorem-new.txt │ ├── lorem.txt │ └── lorem2.txt └── target ├── classes ├── META-INF │ ├── MANIFEST.MF │ └── maven │ │ └── com.laz │ │ └── filesync │ │ ├── pom.properties │ │ └── pom.xml ├── com │ └── laz │ │ └── filesync │ │ ├── FileSyncMain.class │ │ ├── client │ │ ├── FileSendClient$1.class │ │ ├── FileSendClient.class │ │ ├── FileSyncClient$1.class │ │ ├── FileSyncClient.class │ │ ├── file │ │ │ └── handler │ │ │ │ └── MessageEncoder.class │ │ ├── handler │ │ │ └── MsgClientHandler.class │ │ └── msg │ │ │ ├── DiffFilesSyncMsg.class │ │ │ └── RequestMsg.class │ │ ├── conf │ │ └── Configuration.class │ │ ├── msg │ │ ├── BaseMsg.class │ │ ├── ErrorMsg$Code.class │ │ ├── ErrorMsg.class │ │ └── MsgType.class │ │ ├── rysnc │ │ ├── checksums │ │ │ ├── BlockChecksums.class │ │ │ ├── DiffCheckItem.class │ │ │ ├── DiffFileMeta.class │ │ │ ├── FileChecksums.class │ │ │ └── RollingChecksum.class │ │ └── util │ │ │ ├── ByteTool.class │ │ │ ├── Constants.class │ │ │ ├── QuickMD5.class │ │ │ ├── RsyncException.class │ │ │ └── RsyncFileUtils.class │ │ ├── server │ │ ├── FileReceiveServer$1.class │ │ ├── FileReceiveServer.class │ │ ├── FileSyncServer$1.class │ │ ├── FileSyncServer.class │ │ ├── file │ │ │ └── handler │ │ │ │ ├── FileReceiveServerHandler.class │ │ │ │ └── FileSendClientHandler.class │ │ ├── handler │ │ │ └── MsgServerHandler.class │ │ └── msg │ │ │ ├── FileCheckSumsMsg.class │ │ │ └── FileInfo.class │ │ └── util │ │ ├── Coder.class │ │ ├── Constants.class │ │ ├── FileSyncUtil$1.class │ │ ├── FileSyncUtil.class │ │ ├── JsonUtil$1.class │ │ ├── JsonUtil$2.class │ │ ├── JsonUtil.class │ │ └── ZipUtils.class ├── log4j.properties ├── small_client.txt └── small_server.txt └── test-classes ├── com └── laz │ └── filesync │ └── test │ └── TestSync.class ├── diff ├── lorem ├── lorem-new.txt ├── lorem.txt └── lorem2.txt /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | filesync 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/resources=UTF-8 4 | encoding//src/test/java=UTF-8 5 | encoding//src/test/resources=UTF-8 6 | encoding/=UTF-8 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.8 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 13 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 14 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 15 | org.eclipse.jdt.core.compiler.release=disabled 16 | org.eclipse.jdt.core.compiler.source=1.8 17 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.springframework.ide.eclipse.xml.namespaces.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.springframework.ide.eclipse.xml.namespaces.default.version.check.classpath=true 3 | org.springframework.ide.eclipse.xml.namespaces.enable.project.preferences=false 4 | org.springframework.ide.eclipse.xml.namespaces.loadNamespaceHandlerFromClasspath=true 5 | org.springframework.ide.eclipse.xml.namespaces.use.https.for.new.namespace.locations=false 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 纯Java实现的基于rsync算法的文件增量同步工具 2 | 3 | ### 作用: 4 | 该工具利用rsync算法实现了两个文件夹资源差异分析,生成差异文件,进行同步,对应两个文件夹差异不大的情况,提高了同步效率和减少了服务端与客服端文件资源传输量 5 | 6 | ### 思路: 7 | ![](./design/flow.png) 8 | 9 | ### 参数说明 10 | -m 以客户端还是服务端模式启动 server:服务端 client:客服端 11 | 12 | -p -port 运行端口 13 | 14 | -filePort 文件传输监听端口 15 | 16 | -h 客服端需要连接的服务端地址 17 | 18 | -clientPath 客服端同步目录地址 19 | 20 | -serverPath 服务端同步目录地址 21 | 22 | -clean 清空生成缓存文件 23 | 24 | ### 使用示例 25 | 利用maven命令生成工具包: 26 | 27 | mvn package 28 | 服务端: 29 | 30 | java -jar filesync-0.0.1-SNAPSHOT.jar -m server -port 8989 -fileport 8990 31 | 32 | 客服端: 33 | 34 | java -jar filesync-0.0.1-SNAPSHOT.jar -m client -h 172.18.194.117 -clientPath "D:\\server\\apache-tomcat-8.0.36-2\\webapps\\SMS" -serverPath "/home/nsms/test_file_sync/SMS/" 35 | -------------------------------------------------------------------------------- /design/design.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/design/design.pptx -------------------------------------------------------------------------------- /design/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/design/flow.png -------------------------------------------------------------------------------- /logs/error.log: -------------------------------------------------------------------------------- 1 | [ERROR] 2020-07-17 09:47:53,909 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:295) 2 | 错误原因:远程主机强迫关闭了一个现有的连接。 3 | [ERROR] 2020-07-17 09:58:29,811 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:295) 4 | 错误原因:远程主机强迫关闭了一个现有的连接。 5 | [ERROR] 2020-07-17 10:00:14,623 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:264) 6 | 错误原因:远程主机强迫关闭了一个现有的连接。 7 | [ERROR] 2020-07-17 10:05:46,590 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:295) 8 | 错误原因:远程主机强迫关闭了一个现有的连接。 9 | -------------------------------------------------------------------------------- /logs/error.log.2020-07-10: -------------------------------------------------------------------------------- 1 | [ERROR] 2020-07-10 14:41:21,578 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:93) 2 | 错误原因:远程主机强迫关闭了一个现有的连接。 3 | [ERROR] 2020-07-10 14:42:44,207 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:93) 4 | 错误原因:远程主机强迫关闭了一个现有的连接。 5 | [ERROR] 2020-07-10 14:44:31,849 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:93) 6 | 错误原因:远程主机强迫关闭了一个现有的连接。 7 | [ERROR] 2020-07-10 14:45:26,121 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:93) 8 | 错误原因:远程主机强迫关闭了一个现有的连接。 9 | [ERROR] 2020-07-10 14:45:37,680 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:93) 10 | 错误原因:远程主机强迫关闭了一个现有的连接。 11 | [ERROR] 2020-07-10 14:54:34,358 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:93) 12 | 错误原因:远程主机强迫关闭了一个现有的连接。 13 | [ERROR] 2020-07-10 15:06:59,790 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:75) 14 | 错误原因:远程主机强迫关闭了一个现有的连接。 15 | [ERROR] 2020-07-10 17:08:39,105 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 16 | 错误原因:远程主机强迫关闭了一个现有的连接。 17 | [ERROR] 2020-07-10 17:12:08,694 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 18 | 错误原因:远程主机强迫关闭了一个现有的连接。 19 | [ERROR] 2020-07-10 17:15:07,828 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 20 | 错误原因:远程主机强迫关闭了一个现有的连接。 21 | [ERROR] 2020-07-10 17:15:32,535 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 22 | 错误原因:远程主机强迫关闭了一个现有的连接。 23 | [ERROR] 2020-07-10 17:17:06,836 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 24 | 错误原因:远程主机强迫关闭了一个现有的连接。 25 | [ERROR] 2020-07-10 18:03:25,128 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 26 | 错误原因:远程主机强迫关闭了一个现有的连接。 27 | [ERROR] 2020-07-10 18:04:55,136 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:172) 28 | 错误原因:远程主机强迫关闭了一个现有的连接。 29 | [ERROR] 2020-07-10 18:06:42,688 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 30 | 错误原因:Adjusted frame length exceeds 1048576: 1347093256 - discarded 31 | -------------------------------------------------------------------------------- /logs/error.log.2020-07-13: -------------------------------------------------------------------------------- 1 | [ERROR] 2020-07-13 09:28:56,421 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 2 | 错误原因:远程主机强迫关闭了一个现有的连接。 3 | [ERROR] 2020-07-13 09:29:20,092 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:96) 4 | 错误原因:readerIndex(0) + length(1024) exceeds writerIndex(104): PooledUnsafeDirectByteBuf(ridx: 0, widx: 104, cap: 1024) 5 | [ERROR] 2020-07-13 09:33:40,487 method:com.laz.filesync.server.handler.FileServerHandler.exceptionCaught(FileServerHandler.java:46) 6 | 错误原因:readerIndex(0) + length(1024) exceeds writerIndex(104): PooledUnsafeDirectByteBuf(ridx: 0, widx: 104, cap: 1024) 7 | [ERROR] 2020-07-13 09:37:16,758 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:172) 8 | 错误原因:远程主机强迫关闭了一个现有的连接。 9 | [ERROR] 2020-07-13 10:55:37,519 method:com.laz.filesync.server.handler.FileServerHandler.exceptionCaught(FileServerHandler.java:34) 10 | 错误原因:远程主机强迫关闭了一个现有的连接。 11 | [ERROR] 2020-07-13 10:56:02,086 method:com.laz.filesync.server.handler.FileServerHandler.exceptionCaught(FileServerHandler.java:34) 12 | 错误原因:远程主机强迫关闭了一个现有的连接。 13 | [ERROR] 2020-07-13 10:56:16,448 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:172) 14 | 错误原因:远程主机强迫关闭了一个现有的连接。 15 | [ERROR] 2020-07-13 10:57:28,570 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:172) 16 | 错误原因:远程主机强迫关闭了一个现有的连接。 17 | [ERROR] 2020-07-13 10:58:38,479 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:172) 18 | 错误原因:远程主机强迫关闭了一个现有的连接。 19 | [ERROR] 2020-07-13 11:00:40,937 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:172) 20 | 错误原因:远程主机强迫关闭了一个现有的连接。 21 | [ERROR] 2020-07-13 11:02:24,406 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:97) 22 | 错误原因:Adjusted frame length exceeds 1048576: 1347093256 - discarded 23 | [ERROR] 2020-07-13 11:12:07,716 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:97) 24 | 错误原因:Adjusted frame length exceeds 1048576: 1347093256 - discarded 25 | [ERROR] 2020-07-13 11:13:54,083 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:97) 26 | 错误原因:Adjusted frame length exceeds 1048576: 1347093256 - discarded 27 | [ERROR] 2020-07-13 11:15:06,466 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:97) 28 | 错误原因:Adjusted frame length exceeds 1048576: 1347093256 - discarded 29 | [ERROR] 2020-07-13 11:15:36,683 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:97) 30 | 错误原因:Adjusted frame length exceeds 1048576: 1347093256 - discarded 31 | [ERROR] 2020-07-13 11:17:46,138 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:97) 32 | 错误原因:Adjusted frame length exceeds 1048576: 1347093256 - discarded 33 | [ERROR] 2020-07-13 11:19:46,171 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:172) 34 | 错误原因:远程主机强迫关闭了一个现有的连接。 35 | [ERROR] 2020-07-13 11:57:35,012 method:com.laz.filesync.server.handler.FileServerHandler.exceptionCaught(FileServerHandler.java:34) 36 | 错误原因:远程主机强迫关闭了一个现有的连接。 37 | [ERROR] 2020-07-13 17:55:26,821 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:200) 38 | 错误原因:Cannot assign requested address: connect: /127.0.0.1:0 39 | [ERROR] 2020-07-13 18:01:03,188 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:125) 40 | 错误原因:远程主机强迫关闭了一个现有的连接。 41 | [ERROR] 2020-07-13 18:07:55,886 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:125) 42 | 错误原因:远程主机强迫关闭了一个现有的连接。 43 | [ERROR] 2020-07-13 18:09:22,746 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:125) 44 | 错误原因:远程主机强迫关闭了一个现有的连接。 45 | [ERROR] 2020-07-13 18:10:18,908 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:125) 46 | 错误原因:远程主机强迫关闭了一个现有的连接。 47 | [ERROR] 2020-07-13 18:10:55,721 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:125) 48 | 错误原因:远程主机强迫关闭了一个现有的连接。 49 | [ERROR] 2020-07-13 18:12:42,262 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:206) 50 | 错误原因:远程主机强迫关闭了一个现有的连接。 51 | [ERROR] 2020-07-13 18:14:00,506 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:125) 52 | 错误原因:远程主机强迫关闭了一个现有的连接。 53 | [ERROR] 2020-07-13 18:14:45,268 method:com.laz.filesync.server.handler.MsgServerHandler.exceptionCaught(MsgServerHandler.java:125) 54 | 错误原因:远程主机强迫关闭了一个现有的连接。 55 | [ERROR] 2020-07-13 18:15:15,668 method:com.laz.filesync.client.handler.MsgClientHandler.exceptionCaught(MsgClientHandler.java:206) 56 | 错误原因:远程主机强迫关闭了一个现有的连接。 57 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | com.laz 6 | filesync 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | UTF-8 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | 18 | commons-codec 19 | commons-codec 20 | 1.13 21 | 22 | 23 | 24 | io.netty 25 | netty-all 26 | 4.1.42.Final 27 | 28 | 29 | 30 | commons-cli 31 | commons-cli 32 | 1.4 33 | 34 | 35 | org.slf4j 36 | slf4j-api 37 | 1.7.25 38 | 39 | 40 | 41 | 42 | org.slf4j 43 | slf4j-log4j12 44 | 1.7.25 45 | 46 | 47 | 48 | 49 | com.alibaba 50 | fastjson 51 | 1.2.58 52 | 53 | 54 | org.apache.commons 55 | commons-io 56 | 1.3.2 57 | 58 | 59 | 60 | 61 | src/main/java 62 | 63 | 64 | src/main/resources 65 | 66 | **/*.java 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-surefire-plugin 75 | 76 | true 77 | 78 | 79 | 80 | maven-compiler-plugin 81 | 3.1 82 | 83 | 1.8 84 | 1.8 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-shade-plugin 90 | 91 | 92 | package 93 | 94 | shade 95 | 96 | 97 | 98 | 99 | *:* 100 | 101 | META-INF/*.SF 102 | META-INF/*.DSA 103 | META-INF/*.RSA 104 | 105 | 106 | 107 | 108 | 109 | com.laz.filesync.FileSyncMain 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/FileSyncMain.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.List; 6 | 7 | import org.apache.commons.cli.CommandLine; 8 | import org.apache.commons.cli.CommandLineParser; 9 | import org.apache.commons.cli.DefaultParser; 10 | import org.apache.commons.cli.Options; 11 | import org.apache.commons.cli.ParseException; 12 | import org.apache.commons.io.FileUtils; 13 | import org.apache.commons.io.IOUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import com.laz.filesync.client.FileSyncClient; 18 | import com.laz.filesync.conf.Configuration; 19 | import com.laz.filesync.server.FileSyncServer; 20 | import com.laz.filesync.util.Constants; 21 | import com.laz.filesync.util.FileSyncUtil; 22 | 23 | public class FileSyncMain { 24 | private static Logger logger = LoggerFactory.getLogger(FileSyncMain.class); 25 | public static void main(String[] args) { 26 | try { 27 | Configuration conf = parseArgs(args); 28 | new FileSyncMain().start(conf); 29 | } catch (Exception e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | private void start(Configuration conf) { 34 | if (conf.isClean()) { 35 | logger.info("开始清空缓存文件"); 36 | long start = System.currentTimeMillis(); 37 | List tempFile = FileSyncUtil.getServerTempFolder(); 38 | for (File file : tempFile) { 39 | if (file.isDirectory()) { 40 | try { 41 | logger.info("清除:"+file.getAbsolutePath()); 42 | FileUtils.deleteDirectory(file); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } 46 | } else { 47 | file.delete(); 48 | } 49 | } 50 | long end = System.currentTimeMillis(); 51 | logger.info("结束。耗时"+(end-start)+"ms"); 52 | return ; 53 | } 54 | if (Constants.SERVER_MODE.equals(conf.getMode())) { 55 | new FileSyncServer(conf).start(); 56 | } else if (Constants.CLIENT_MODE.equals(conf.getMode())) { 57 | new FileSyncClient(conf).start(); 58 | } else { 59 | logger.info("模式输入不正确"); 60 | } 61 | } 62 | private static Configuration parseArgs(String[] args) throws ParseException { 63 | Configuration conf = new Configuration(); 64 | CommandLineParser parser = new DefaultParser(); 65 | Options options = new Options(); 66 | options.addOption("m", "mode", true, "以客户端还是服务端模式启动 server:服务端 client:客服端"); 67 | options.addOption("p", "port", true, "运行端口"); 68 | options.addOption("filePort", true, "文件传输监听端口"); 69 | options.addOption("h", "host", true, "客服端需要连接的服务端地址"); 70 | options.addOption("clientPath", true, "客服端同步目录地址"); 71 | options.addOption("serverPath", true, "服务端同步目录地址"); 72 | options.addOption("clean", false, "清空生成缓存文件"); 73 | CommandLine commandLine = parser.parse(options, args);//解析参数 74 | 75 | if (commandLine.hasOption("clean")) { 76 | conf.setClean(true); 77 | return conf; 78 | } 79 | if (!commandLine.hasOption("m")) { 80 | throw new RuntimeException("请输入模式"); 81 | } 82 | conf.setMode(commandLine.getOptionValue("m")); 83 | if (commandLine.hasOption("p")) { 84 | conf.setPort(Integer.parseInt(commandLine.getOptionValue("p"))); 85 | } 86 | if (commandLine.hasOption("h")) { 87 | conf.setServerIP(commandLine.getOptionValue("h")); 88 | } 89 | if (commandLine.hasOption("clientPath")) { 90 | conf.setClientPath(commandLine.getOptionValue("clientPath")); 91 | } 92 | if (commandLine.hasOption("serverPath")) { 93 | conf.setServerPath(commandLine.getOptionValue("serverPath")); 94 | } 95 | if (commandLine.hasOption("filePort")) { 96 | conf.setFilePort(Integer.parseInt(commandLine.getOptionValue("filePort"))); 97 | } 98 | return conf; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/client/FileSendClient.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.client; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.laz.filesync.client.file.handler.MessageEncoder; 7 | 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.channel.Channel; 10 | import io.netty.channel.ChannelFuture; 11 | import io.netty.channel.ChannelInitializer; 12 | import io.netty.channel.ChannelPipeline; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.nio.NioSocketChannel; 15 | 16 | public class FileSendClient { 17 | private String host; 18 | private int port; 19 | private static Logger logger = LoggerFactory.getLogger(FileSendClient.class); 20 | public FileSendClient(String host,int port) { 21 | this.host = host; 22 | this.port = port==0?8990:port; 23 | } 24 | private Channel channel; 25 | private NioEventLoopGroup group; 26 | public NioEventLoopGroup getGroup() { 27 | return group; 28 | } 29 | public Channel getChannel() { 30 | return channel; 31 | } 32 | public void start() { 33 | Bootstrap bootstrap = new Bootstrap(); 34 | NioEventLoopGroup group = new NioEventLoopGroup(); 35 | this.group = group; 36 | bootstrap.group(group) 37 | .channel(NioSocketChannel.class) 38 | .handler(new ChannelInitializer() { 39 | @Override 40 | protected void initChannel(NioSocketChannel channel) throws Exception { 41 | ChannelPipeline pipeline = channel.pipeline(); 42 | pipeline.addLast(new MessageEncoder()); 43 | // pipeline.addLast(new FileSendClientHandler()); 44 | 45 | } 46 | }); 47 | 48 | ChannelFuture future; 49 | try { 50 | future = bootstrap.connect(host, port).sync(); 51 | if (future.isSuccess()) { 52 | logger.info("文件服务器连接成功"); 53 | this.channel = future.channel(); 54 | } else { 55 | logger.info("文件服务器连接失败"); 56 | } 57 | } catch (InterruptedException e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | 62 | public static void main(String[] args) { 63 | new FileSendClient("192.168.5.13",8990).start(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/client/FileSyncClient.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.client; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.laz.filesync.client.handler.MsgClientHandler; 7 | import com.laz.filesync.client.msg.RequestMsg; 8 | import com.laz.filesync.conf.Configuration; 9 | import com.laz.filesync.server.FileSyncServer; 10 | import com.laz.filesync.server.handler.MsgServerHandler; 11 | import com.laz.filesync.util.Constants; 12 | 13 | import io.netty.bootstrap.Bootstrap; 14 | import io.netty.channel.Channel; 15 | import io.netty.channel.ChannelFuture; 16 | import io.netty.channel.ChannelFutureListener; 17 | import io.netty.channel.ChannelHandlerContext; 18 | import io.netty.channel.ChannelInboundHandlerAdapter; 19 | import io.netty.channel.ChannelInitializer; 20 | import io.netty.channel.ChannelPipeline; 21 | import io.netty.channel.EventLoopGroup; 22 | import io.netty.channel.nio.NioEventLoopGroup; 23 | import io.netty.channel.socket.SocketChannel; 24 | import io.netty.channel.socket.nio.NioSocketChannel; 25 | import io.netty.handler.codec.LineBasedFrameDecoder; 26 | import io.netty.handler.codec.serialization.ClassResolvers; 27 | import io.netty.handler.codec.serialization.ObjectDecoder; 28 | import io.netty.handler.codec.serialization.ObjectEncoder; 29 | import io.netty.handler.codec.string.StringDecoder; 30 | import io.netty.handler.codec.string.StringEncoder; 31 | 32 | public class FileSyncClient { 33 | private int port; 34 | private String ip; 35 | private Configuration conf; 36 | private static Logger logger = LoggerFactory.getLogger(FileSyncClient.class); 37 | 38 | public FileSyncClient(Configuration conf) { 39 | this.conf = conf; 40 | init(); 41 | } 42 | private void init() { 43 | this.port = conf.getPort() == 0 ? 8989 : conf.getPort(); 44 | this.ip = conf.getServerIP(); 45 | if (this.ip == null) { 46 | throw new RuntimeException("未找到服务端IP"); 47 | } 48 | } 49 | public void start() { 50 | EventLoopGroup group = new NioEventLoopGroup(); 51 | Bootstrap bootstrap = new Bootstrap(); 52 | bootstrap.group(group).channel(NioSocketChannel.class) 53 | .handler(new ChannelInitializer(){ 54 | @Override 55 | protected void initChannel(SocketChannel ch) throws Exception { 56 | ChannelPipeline pipeline = ch.pipeline(); 57 | //输入Handler 58 | pipeline.addLast("decoder", new ObjectDecoder(Constants.OBJECT_SIZE_LIMIT, 59 | ClassResolvers.cacheDisabled(this.getClass().getClassLoader()))); 60 | //输出Handler 61 | pipeline.addLast("encoder", new ObjectEncoder()); 62 | 63 | 64 | MsgClientHandler hanler = new MsgClientHandler(); 65 | hanler.setConf(conf); 66 | pipeline.addLast("handler", hanler); 67 | } 68 | }); 69 | 70 | try { 71 | ChannelFuture future = bootstrap.connect(ip,port).sync(); 72 | logger.info("------------客服端启动----------------"); 73 | future.channel().closeFuture().sync(); 74 | } catch (Exception e) { 75 | e.printStackTrace(); 76 | } finally { 77 | group.shutdownGracefully(); 78 | } 79 | } 80 | 81 | public static void main(String[] args) { 82 | Configuration conf = new Configuration(); 83 | conf.setClientPath("d:/filesync/client"); 84 | conf.setServerPath("d:/filesync/server"); 85 | conf.setServerIP("127.0.0.1"); 86 | new FileSyncClient(conf).start(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/client/file/handler/MessageEncoder.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.client.file.handler; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import com.laz.filesync.server.msg.FileInfo; 6 | import com.laz.filesync.util.FileSyncUtil; 7 | 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.ChannelPromise; 11 | import io.netty.channel.DefaultFileRegion; 12 | import io.netty.handler.codec.MessageToByteEncoder; 13 | import io.netty.util.ReferenceCountUtil; 14 | 15 | public class MessageEncoder extends MessageToByteEncoder { 16 | public static final Logger logger = Logger.getLogger(MessageEncoder.class); 17 | @Override 18 | protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { 19 | if (msg instanceof DefaultFileRegion) { 20 | DefaultFileRegion file = (DefaultFileRegion)msg; 21 | ReferenceCountUtil.retain(file);//这行 22 | ctx.write(file); 23 | } else if(msg instanceof FileInfo){ 24 | FileInfo info = (FileInfo)msg; 25 | byte[] fileNames = info.getFilename().getBytes(); 26 | out.writeInt(FileSyncUtil.STAERT_FLAG); 27 | out.writeInt(fileNames.length); 28 | out.writeBytes(fileNames); 29 | out.writeLong(info.getLength()); 30 | } else { 31 | logger.error("无法识别的消息类型"+msg); 32 | } 33 | } 34 | @Override 35 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 36 | super.close(ctx, promise); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/client/handler/MsgClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.client.handler; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.util.ArrayList; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | import org.apache.commons.io.IOUtils; 13 | import org.apache.log4j.Logger; 14 | 15 | import com.laz.filesync.client.FileSendClient; 16 | import com.laz.filesync.client.msg.DiffFilesSyncMsg; 17 | import com.laz.filesync.client.msg.RequestMsg; 18 | import com.laz.filesync.conf.Configuration; 19 | import com.laz.filesync.msg.BaseMsg; 20 | import com.laz.filesync.msg.ErrorMsg; 21 | import com.laz.filesync.rysnc.checksums.DiffCheckItem; 22 | import com.laz.filesync.rysnc.checksums.FileChecksums; 23 | import com.laz.filesync.rysnc.checksums.RollingChecksum; 24 | import com.laz.filesync.rysnc.util.Constants; 25 | import com.laz.filesync.rysnc.util.RsyncFileUtils; 26 | import com.laz.filesync.server.msg.FileCheckSumsMsg; 27 | import com.laz.filesync.server.msg.FileInfo; 28 | import com.laz.filesync.util.Coder; 29 | import com.laz.filesync.util.FileSyncUtil; 30 | import com.laz.filesync.util.FileUtil; 31 | import com.laz.filesync.util.PathMap; 32 | import com.laz.filesync.util.ZipUtils; 33 | 34 | import io.netty.channel.Channel; 35 | import io.netty.channel.ChannelHandlerContext; 36 | import io.netty.channel.DefaultFileRegion; 37 | import io.netty.channel.SimpleChannelInboundHandler; 38 | 39 | public class MsgClientHandler extends SimpleChannelInboundHandler { 40 | public static final Logger logger = Logger.getLogger(MsgClientHandler.class); 41 | private Configuration conf; 42 | // 操作系统识别的换行符 43 | private static final String CR = System.getProperty("line.separator"); 44 | 45 | public void setConf(Configuration conf) { 46 | this.conf = conf; 47 | } 48 | 49 | public Configuration getConf() { 50 | return conf; 51 | } 52 | 53 | @Override 54 | protected void channelRead0(ChannelHandlerContext ctx, BaseMsg msg) throws Exception { 55 | switch (msg.getType()) { 56 | case REQUEST: { 57 | 58 | } 59 | break; 60 | case CHECK_SUM: { 61 | FileCheckSumsMsg checksumsMsg = (FileCheckSumsMsg) msg; 62 | if (!checkExitDiff(checksumsMsg)) { 63 | logger.info("与服务端目录存在差异,开始进行文件同步"); 64 | File tempFolder = getTempFolder(); 65 | //存储不需要同步的文件路径信息 66 | Set noSynsFileSets = new HashSet(); 67 | // 处理消息,获取最终差异文件zip包路径 68 | String zipPath = dealChecksumsMsg(tempFolder, checksumsMsg,noSynsFileSets); 69 | sendFile(ctx, zipPath,noSynsFileSets); 70 | } else { 71 | logger.info("服务端与客服端无差异,同步成功"); 72 | ctx.close(); 73 | } 74 | } 75 | break; 76 | case SYNC: { 77 | 78 | } 79 | break; 80 | case ERROR: { 81 | ErrorMsg error = (ErrorMsg) msg; 82 | logger.error("code" + error.getCode() + " msg:" + error.getMsg()); 83 | ctx.channel().close(); 84 | } 85 | break; 86 | default: 87 | break; 88 | 89 | } 90 | } 91 | 92 | private boolean checkExitDiff(FileCheckSumsMsg checksumsMsg) throws Exception { 93 | Map clientChecksums = new PathMap(); 94 | File clientFolder = new File(conf.getClientPath()); 95 | logger.info("生成客服端文件检验和信息"); 96 | long start = System.currentTimeMillis(); 97 | FileSyncUtil.getFileCheckSums(clientFolder, clientFolder, clientChecksums); 98 | long end = System.currentTimeMillis(); 99 | logger.info("客服端文件检验和信息生成完成" + (end - start) + "ms"); 100 | return checkChecksums(checksumsMsg.getChecksumsMap(), clientChecksums); 101 | } 102 | 103 | private boolean checkChecksums(Map serverSums, Map clientSums) 104 | throws Exception { 105 | if (serverSums.size() != clientSums.size()) { 106 | return false; 107 | } 108 | for (String k : serverSums.keySet()) { 109 | FileChecksums serverCheck = serverSums.get(k); 110 | FileChecksums clientCheck = clientSums.get(k); 111 | if (clientCheck == null || !Coder.encryptBASE64(clientCheck.getChecksum()) 112 | .equals(Coder.encryptBASE64(serverCheck.getChecksum()))) { 113 | return false; 114 | } 115 | } 116 | return true; 117 | } 118 | 119 | private void sendFile(ChannelHandlerContext ctx, String zipPath, Set noSynsFileSets) { 120 | new Thread(new Runnable() { 121 | @Override 122 | public void run() { 123 | FileSendClient fileClient = connectFileSever(); 124 | try { 125 | Channel channel = fileClient.getChannel(); 126 | if (channel != null && channel.isActive()) { 127 | File file = new File(zipPath); 128 | logger.info("总共传输差异文件容量= " + FileSyncUtil.getDoubleValue((double) file.length() / 1024 / 1024) 129 | + "m"); 130 | DefaultFileRegion fileRegion = new DefaultFileRegion(file, 0, file.length()); 131 | FileInfo info = new FileInfo(); 132 | info.setFilename(file.getName()); 133 | info.setLength(file.length()); 134 | channel.writeAndFlush(info); 135 | System.out.println("线程名:" + Thread.currentThread().getName()); 136 | channel.writeAndFlush(fileRegion).addListener(future -> { 137 | if (future.isSuccess()) { 138 | String checksum = Coder.encryptBASE64(FileSyncUtil.generateFileDigest(file)); 139 | logger.info(file.getAbsolutePath() + "文件传输完成。检验码:"+checksum); 140 | // 通知服务端进行md5验证传输完整性,并进行文件合并 141 | DiffFilesSyncMsg msg = new DiffFilesSyncMsg(); 142 | msg.setFileDigest(checksum); 143 | msg.setLength(file.length()); 144 | msg.setFileName(file.getName()); 145 | msg.setNoSynsFileSets(noSynsFileSets); 146 | msg.setServerPath(conf.getServerPath()); 147 | ctx.writeAndFlush(msg); 148 | } 149 | }); 150 | } else { 151 | logger.error("连接文件服务器失败"); 152 | } 153 | channel.closeFuture().sync(); 154 | } catch (Exception e) { 155 | e.printStackTrace(); 156 | } finally { 157 | logger.info("关闭与文件传输服务端连接"); 158 | fileClient.getGroup().shutdownGracefully(); 159 | } 160 | 161 | } 162 | }).start(); 163 | 164 | } 165 | 166 | private FileSendClient connectFileSever() { 167 | FileSendClient fileClient = new FileSendClient(conf.getServerIP(), conf.getFilePort()); 168 | fileClient.start(); 169 | return fileClient; 170 | } 171 | 172 | private File getTempFolder() { 173 | String tempPath = System.getProperty("java.io.tmpdir"); 174 | File tempFolder = new File(tempPath); 175 | if (!tempFolder.exists()) { 176 | tempFolder.mkdirs(); 177 | } 178 | // 生成本次需同步的差异文件存放目录 179 | File diffFolder = new File(tempFolder + File.separator + "diff-" + FileSyncUtil.getTimeStr()); 180 | if (diffFolder.exists()) { 181 | diffFolder.delete(); 182 | } 183 | diffFolder.mkdir(); 184 | return diffFolder; 185 | } 186 | 187 | @Override 188 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 189 | super.channelActive(ctx); 190 | // 建立连接请求信息 191 | RequestMsg msg = new RequestMsg(); 192 | msg.setFolderPath(conf.getServerPath()); 193 | ctx.writeAndFlush(msg); 194 | } 195 | 196 | private String dealChecksumsMsg(File tempFolder, FileCheckSumsMsg checksumsMsg, Set noSynsFileSets) { 197 | logger.info("收到服务端的文件检验和集信息" + checksumsMsg.getChecksumsMap().size()); 198 | 199 | // 根据检验和生成差异文件信息 200 | String path = conf.getClientPath(); 201 | File folder = new File(path); 202 | if (folder.exists() && folder.isDirectory()) { 203 | try { 204 | logger.info("打印rsync算法超过5s的文件"); 205 | 206 | generateDiffFile(folder, folder, checksumsMsg.getChecksumsMap(), tempFolder,noSynsFileSets); 207 | logger.info("生成同步差异文件到缓存目录" + tempFolder.getAbsolutePath()); 208 | // 形成压缩包 209 | String zipPath = tempFolder.getAbsolutePath() + ".zip"; 210 | FileOutputStream output = new FileOutputStream(new File(zipPath)); 211 | ZipUtils.toZip(tempFolder.getAbsolutePath(), output, true); 212 | return zipPath; 213 | } catch (Exception e) { 214 | e.printStackTrace(); 215 | } 216 | } else { 217 | logger.error("客服端文件目录不存在或者文件不是目录"); 218 | } 219 | return ""; 220 | } 221 | 222 | private void generateDiffFile(File root, File f, Map checksumsMap, File tempFolder, Set noSynsFileSets) 223 | throws Exception { 224 | if (f.isDirectory()) { 225 | for (File file : f.listFiles()) { 226 | generateDiffFile(root, file, checksumsMap, tempFolder,noSynsFileSets); 227 | } 228 | } else { 229 | long start = System.currentTimeMillis(); 230 | String rootPath = root.getAbsolutePath(); 231 | String path = FileUtil.getRelativePath(f, rootPath); 232 | FileChecksums check = checksumsMap.get(FileUtil.convertPath(path)); 233 | FileChecksums sourceCheckSum = new FileChecksums(f,false); 234 | //先判断文件md5是否一致 235 | if (check!=null && Coder.encryptBASE64(check.getChecksum()).equals(Coder.encryptBASE64(sourceCheckSum.getChecksum()))) { 236 | //logger.info(f.getAbsolutePath()+"文件检验和一致,不需要同步"); 237 | noSynsFileSets.add(FileUtil.convertPath(path)); 238 | } else { 239 | // 滚动获取文件之间的差异信息 240 | List diffList = rollGetDiff(root, f, checksumsMap); 241 | long end = System.currentTimeMillis(); 242 | if ((end-start)>5000) { 243 | logger.info("滚动计算"+f.getAbsoluteFile()+": spend time :" + (long) (end - start) + "ms"); 244 | } 245 | generateDiffFileOnTempFolder(root, f, diffList, tempFolder,noSynsFileSets); 246 | } 247 | } 248 | 249 | } 250 | 251 | private void generateDiffFileOnTempFolder(File root, File f, List diffList, File tempFolder, Set noSynsFileSets) 252 | throws Exception { 253 | String rootPath = root.getAbsolutePath(); 254 | String filePath = f.getAbsolutePath(); 255 | String path = FileUtil.getRelativePath(f, rootPath); 256 | File tempDiffFile = new File(tempFolder + File.separator + path); 257 | FileUtil.createFile(tempDiffFile); 258 | if (noSynsFileSets.contains(FileUtil.convertPath(path))){ 259 | return ; 260 | } else if (diffList == null) { 261 | // 不存在diff,说明服务端不存在改文件,直接加入同步目录 262 | FileInputStream in = new FileInputStream(new File(filePath)); 263 | FileOutputStream out = new FileOutputStream(tempDiffFile); 264 | IOUtils.copy(in, out); 265 | IOUtils.closeQuietly(in); 266 | IOUtils.closeQuietly(out); 267 | } else { 268 | // 生成临时变量文件 269 | RsyncFileUtils.createRsyncFile(diffList, tempDiffFile, Constants.BLOCK_SIZE); 270 | } 271 | 272 | } 273 | 274 | private List rollGetDiff(File root, File f, Map checksumsMap) 275 | throws Exception { 276 | String rootPath = root.getAbsolutePath(); 277 | String path = FileUtil.getRelativePath(f, rootPath); 278 | FileChecksums check = checksumsMap.get(FileUtil.convertPath(path)); 279 | List diffList = new ArrayList(); 280 | if (check != null) { 281 | RollingChecksum rck = new RollingChecksum(check, f, diffList); 282 | rck.rolling(); 283 | } else { 284 | return null; 285 | } 286 | return diffList; 287 | } 288 | 289 | /** 290 | * 异常错误处理 291 | */ 292 | @Override 293 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 294 | cause.printStackTrace(); 295 | logger.error("错误原因:" + cause.getMessage()); 296 | ctx.channel().close(); 297 | } 298 | 299 | } 300 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/client/msg/DiffFilesSyncMsg.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.client.msg; 2 | 3 | import java.util.Set; 4 | 5 | import com.laz.filesync.msg.BaseMsg; 6 | import com.laz.filesync.msg.MsgType; 7 | 8 | /** 9 | * 服务端接受有差异的文件信息 10 | * 11 | */ 12 | public class DiffFilesSyncMsg extends BaseMsg{ 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | private String fileDigest; 18 | private long length; 19 | private String fileName; 20 | private String serverPath; 21 | private Set noSynsFileSets; 22 | 23 | public void setNoSynsFileSets(Set noSynsFileSets) { 24 | this.noSynsFileSets = noSynsFileSets; 25 | } 26 | 27 | public Set getNoSynsFileSets() { 28 | return noSynsFileSets; 29 | } 30 | public String getServerPath() { 31 | return serverPath; 32 | } 33 | 34 | public void setServerPath(String serverPath) { 35 | this.serverPath = serverPath; 36 | } 37 | 38 | public String getFileName() { 39 | return fileName; 40 | } 41 | 42 | public void setFileName(String fileName) { 43 | this.fileName = fileName; 44 | } 45 | @Override 46 | public MsgType getType() { 47 | return MsgType.SYNC; 48 | } 49 | 50 | public void setFileDigest(String fileDigest) { 51 | this.fileDigest = fileDigest; 52 | } 53 | 54 | public String getFileDigest() { 55 | return fileDigest; 56 | } 57 | public long getLength() { 58 | return length; 59 | } 60 | public void setLength(long length) { 61 | this.length = length; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/client/msg/RequestMsg.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.client.msg; 2 | 3 | import com.laz.filesync.msg.BaseMsg; 4 | import com.laz.filesync.msg.MsgType; 5 | 6 | public class RequestMsg extends BaseMsg{ 7 | private static final long serialVersionUID = 1L; 8 | private String folderPath; 9 | 10 | public String getFolderPath() { 11 | return folderPath; 12 | } 13 | 14 | public void setFolderPath(String folderPath) { 15 | this.folderPath = folderPath; 16 | } 17 | 18 | @Override 19 | public MsgType getType() { 20 | return MsgType.REQUEST; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/conf/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.conf; 2 | 3 | /** 4 | * @author laz 5 | * 6 | */ 7 | public class Configuration { 8 | //启动模式 9 | private String mode; 10 | //绑定端口 11 | private int port; 12 | //文件传输端口 13 | private int filePort; 14 | //情况缓存文件 15 | private boolean clean; 16 | //服务端地址 17 | private String serverIP; 18 | private String clientPath; 19 | private String serverPath; 20 | 21 | public void setClean(boolean clean) { 22 | this.clean = clean; 23 | } 24 | 25 | public boolean isClean() { 26 | return clean; 27 | } 28 | public void setClientPath(String clientPath) { 29 | this.clientPath = clientPath; 30 | } 31 | public String getClientPath() { 32 | return clientPath; 33 | } 34 | public void setServerPath(String serverPath) { 35 | this.serverPath = serverPath; 36 | } 37 | public String getServerPath() { 38 | return serverPath; 39 | } 40 | 41 | public String getServerIP() { 42 | return serverIP; 43 | } 44 | 45 | public void setServerIP(String serverIP) { 46 | this.serverIP = serverIP; 47 | } 48 | public void setPort(int port) { 49 | this.port = port; 50 | } 51 | public int getPort() { 52 | return port; 53 | } 54 | public String getMode() { 55 | return mode; 56 | } 57 | 58 | public void setMode(String mode) { 59 | this.mode = mode; 60 | } 61 | 62 | public int getFilePort() { 63 | return filePort; 64 | } 65 | public void setFilePort(int filePort) { 66 | this.filePort = filePort; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/msg/BaseMsg.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.msg; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 消息父类 7 | * 8 | */ 9 | public abstract class BaseMsg implements Serializable{ 10 | 11 | /** 12 | * 13 | */ 14 | private static final long serialVersionUID = 1L; 15 | 16 | public abstract MsgType getType(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/msg/ErrorMsg.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.msg; 2 | 3 | public class ErrorMsg extends BaseMsg { 4 | private static final long serialVersionUID = 1L; 5 | 6 | public enum Code { 7 | SERVER_PATH_IS_NOT_FOLDER(1, "服务端目录路径不是目录"); 8 | 9 | private int code; 10 | private String msg; 11 | 12 | private Code(int code, String msg) { 13 | this.code = code; 14 | this.msg = msg; 15 | } 16 | 17 | public int getCode() { 18 | return this.code; 19 | } 20 | 21 | public String getMsg() { 22 | return this.msg; 23 | } 24 | } 25 | private int code; 26 | private String msg; 27 | 28 | public void setMsg(String msg) { 29 | this.msg = msg; 30 | } 31 | 32 | public String getMsg() { 33 | return msg; 34 | } 35 | 36 | public void setCode(int code) { 37 | this.code = code; 38 | } 39 | 40 | public int getCode() { 41 | return code; 42 | } 43 | 44 | @Override 45 | public MsgType getType() { 46 | return MsgType.ERROR; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/msg/MsgType.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.msg; 2 | 3 | public enum MsgType { 4 | REQUEST,CHECK_SUM,SYNC, ERROR 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/checksums/BlockChecksums.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.checksums; 2 | 3 | import java.io.Serializable; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | import java.util.zip.Adler32; 7 | 8 | import org.apache.commons.codec.binary.Hex; 9 | 10 | import com.laz.filesync.rysnc.util.RsyncException; 11 | 12 | /** 13 | * 文件块校验 14 | * 15 | * @author jiuyuehe 16 | * 17 | */ 18 | public class BlockChecksums implements Serializable{ 19 | private static final long serialVersionUID = 6881512258509956424L; 20 | private int index; 21 | private long offset; 22 | private long size; 23 | private long weakChecksum; 24 | private byte[] strongChecksum; 25 | 26 | public BlockChecksums(byte[] buf, long offset, long size) { 27 | this.offset = offset; 28 | this.size = size; 29 | this.weakChecksum = generateWeakChecksum(buf,0,(int)size); 30 | this.strongChecksum = generateStrongChecksum(buf,0,(int)size); 31 | } 32 | 33 | public BlockChecksums(int index,byte[] buf, long offset, long size) { 34 | this.index = index; 35 | this.offset = offset; 36 | this.size = size; 37 | this.weakChecksum = generateWeakChecksum(buf,0,(int)size); 38 | this.strongChecksum = generateStrongChecksum(buf,0,(int)size); 39 | } 40 | 41 | /** 42 | * md5 校验 43 | * @param buf 44 | * @return 45 | */ 46 | private byte[] generateStrongChecksum(byte[] buf,int offset,int len) { 47 | try { 48 | MessageDigest messageDigest = MessageDigest.getInstance("MD5"); 49 | messageDigest.update(buf,offset,len); 50 | return messageDigest.digest(); 51 | } catch (NoSuchAlgorithmException e) { 52 | throw new RsyncException(e); 53 | } 54 | } 55 | 56 | 57 | 58 | /** 59 | * adler32 校验 60 | * @param buf 61 | * @return 62 | */ 63 | private long generateWeakChecksum(byte[] buf, int offset , int length ) { 64 | Adler32 adler32 = new Adler32(); 65 | adler32.update(buf,offset,length); 66 | return adler32.getValue(); 67 | } 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | public long getOffset() { 82 | return offset; 83 | } 84 | 85 | public long getSize() { 86 | return size; 87 | } 88 | 89 | public long getWeakChecksum() { 90 | return weakChecksum; 91 | } 92 | 93 | public byte[] getStrongChecksum() { 94 | return strongChecksum; 95 | } 96 | 97 | public String getHexStrongChecksum() { 98 | return new String(Hex.encodeHex(strongChecksum)); 99 | } 100 | 101 | public int getIndex() { 102 | return index; 103 | } 104 | 105 | public void setIndex(int index) { 106 | this.index = index; 107 | } 108 | 109 | @Override 110 | public String toString() { 111 | return "offset: " + offset + " size: " + size + " weak sum: " 112 | + weakChecksum + " strong sum: " + getHexStrongChecksum(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/checksums/DiffCheckItem.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.checksums; 2 | 3 | import java.util.Arrays; 4 | 5 | public class DiffCheckItem { 6 | 7 | /** 8 | * 是否匹配 9 | */ 10 | private boolean isMatch; 11 | 12 | /** 13 | * 匹配,加入匹配号,设置为long类型,防止srcraf.seek(i*blockSize)超出整数范围,变为负数报错 14 | */ 15 | private long index; 16 | 17 | /** 18 | * 不匹配,写入数据 19 | */ 20 | private byte [] data; 21 | 22 | public boolean isMatch() { 23 | return isMatch; 24 | } 25 | 26 | public void setMatch(boolean isMatch) { 27 | this.isMatch = isMatch; 28 | } 29 | 30 | public long getIndex() { 31 | return index; 32 | } 33 | 34 | public void setIndex(long index) { 35 | this.index = index; 36 | } 37 | 38 | public byte[] getData() { 39 | return data; 40 | } 41 | 42 | public void setData(byte[] data) { 43 | this.data = data; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "DiffCheckList [isMatch=" + isMatch + ", index=" + index 49 | + ", data=" + data.length + "]"; 50 | } 51 | 52 | 53 | 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/checksums/DiffFileMeta.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.checksums; 2 | 3 | import java.util.List; 4 | 5 | public class DiffFileMeta { 6 | private int blockSize; 7 | 8 | private List diffList ; 9 | 10 | public int getBlockSize() { 11 | return blockSize; 12 | } 13 | 14 | public void setBlockSize(int blockSize) { 15 | this.blockSize = blockSize; 16 | } 17 | 18 | public List getDiffList() { 19 | return diffList; 20 | } 21 | 22 | public void setDiffList(List diffList) { 23 | this.diffList = diffList; 24 | } 25 | 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/checksums/FileChecksums.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.checksums; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.Serializable; 8 | import java.security.MessageDigest; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import org.apache.commons.codec.binary.Hex; 14 | 15 | import com.laz.filesync.rysnc.util.Constants; 16 | import com.laz.filesync.rysnc.util.RsyncException; 17 | 18 | /** 19 | * 文件对比 20 | * 21 | */ 22 | public class FileChecksums implements Serializable{ 23 | private static final long serialVersionUID = 9065439598214380323L; 24 | private String name; 25 | private byte[] checksum; 26 | private List blockChecksums = new ArrayList(); 27 | 28 | public FileChecksums(File file) { 29 | this(file,true); 30 | } 31 | 32 | public FileChecksums(File file,boolean blockCheck) { 33 | this.name = file.getName(); 34 | this.checksum = generateFileDigest(file); 35 | if (blockCheck) { 36 | this.blockChecksums = generateBlockChecksums(file); 37 | } 38 | } 39 | 40 | private List generateBlockChecksums(File file) { 41 | List list = new ArrayList(); 42 | FileInputStream fis = null; 43 | try { 44 | fis = new FileInputStream(file); 45 | byte[] buf = new byte[Constants.BLOCK_SIZE]; 46 | int bytesRead = 0; 47 | long offset = 0; 48 | int index = 0; 49 | while ((bytesRead = fis.read(buf)) > 0) { 50 | list.add(new BlockChecksums(index ,buf, offset, bytesRead)); 51 | offset += bytesRead; 52 | index ++; 53 | } 54 | } catch (FileNotFoundException e) { 55 | throw new RsyncException(e); 56 | } catch (IOException e) { 57 | throw new RsyncException(e); 58 | }finally { 59 | try { 60 | fis.close(); 61 | } catch (IOException e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | return list; 66 | } 67 | 68 | /** 69 | * 获取整个文件的MD5 70 | * @param file 71 | * @return 72 | */ 73 | private byte[] generateFileDigest(File file) { 74 | FileInputStream fis = null; 75 | try { 76 | MessageDigest sha = MessageDigest.getInstance(Constants.MD5); 77 | fis = new FileInputStream(file); 78 | byte[] buf = new byte[Constants.BLOCK_SIZE]; 79 | int read = 0; 80 | while ((read = fis.read(buf)) > 0) { 81 | sha.update(buf, 0, read); 82 | } 83 | return sha.digest(); 84 | } catch (IOException e) { 85 | throw new RsyncException(e); 86 | } catch (NoSuchAlgorithmException e) { 87 | throw new RsyncException(e); 88 | } finally { 89 | if (fis != null) { 90 | try { 91 | fis.close(); 92 | } catch (IOException e) { 93 | throw new RsyncException(e); 94 | } 95 | } 96 | } 97 | } 98 | 99 | public String getName() { 100 | return name; 101 | } 102 | 103 | public byte[] getChecksum() { 104 | return checksum; 105 | } 106 | 107 | public List getBlockChecksums() { 108 | return blockChecksums; 109 | } 110 | 111 | @Override 112 | public String toString() { 113 | StringBuilder builder = new StringBuilder(); 114 | builder.append("fileChecksums for: "); 115 | builder.append(getName()); 116 | builder.append("file checksum: "); 117 | builder.append(getHexChecksum()); 118 | return builder.toString(); 119 | } 120 | 121 | public String getHexChecksum() { 122 | return new String(Hex.encodeHex(getChecksum())); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/checksums/RollingChecksum.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.checksums; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.RandomAccessFile; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import com.laz.filesync.rysnc.util.Constants; 17 | import com.laz.filesync.rysnc.util.RsyncException; 18 | 19 | /** 20 | * 21 | * @author jiuyuehe 22 | * 23 | */ 24 | public class RollingChecksum { 25 | private Logger logger = LoggerFactory.getLogger(RollingChecksum.class); 26 | /** 27 | * 原始文件 28 | */ 29 | private FileChecksums srcFile; 30 | 31 | /** 32 | * 修改后的文件 33 | */ 34 | private File updateFile; 35 | 36 | /** 37 | * 存储结果字段 38 | */ 39 | private List diffList; 40 | 41 | /** 42 | * 随机文件读取 43 | */ 44 | private RandomAccessFile raf; 45 | 46 | /** 47 | * 随机文件读取 48 | */ 49 | private RandomAccessFile diffraf; 50 | 51 | public RollingChecksum() { 52 | } 53 | 54 | public RollingChecksum(FileChecksums srcFile, File updateFile, List diffList) { 55 | this.srcFile = srcFile; 56 | this.diffList = diffList; 57 | this.updateFile = updateFile; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "RollingChecksum [srcFile=" + srcFile + "]"; 63 | } 64 | 65 | /** 66 | * 文件info 组装成map 67 | * 68 | * @param srcFile 69 | * @return 70 | */ 71 | private Map converte2Map() { 72 | 73 | List blist = srcFile.getBlockChecksums(); 74 | 75 | Map map = new HashMap(); 76 | 77 | for (BlockChecksums blockChecksums : blist) { 78 | map.put(blockChecksums.getWeakChecksum(), blockChecksums); 79 | } 80 | return map; 81 | } 82 | 83 | /** 84 | * 算法 85 | */ 86 | public void rolling() { 87 | Map srcMap = converte2Map(); 88 | if (diffList == null) { 89 | diffList = new ArrayList(); 90 | } 91 | 92 | long fileLength = updateFile.length(); 93 | // 偏移量 94 | int offset = 0; 95 | do { 96 | offset = checkBlk(srcMap, offset, diffList); 97 | } while (offset < fileLength); 98 | 99 | if (diffraf != null) { 100 | try { 101 | diffraf.close(); 102 | } catch (IOException e) { 103 | e.printStackTrace(); 104 | } 105 | } 106 | 107 | if (raf != null) { 108 | try { 109 | raf.close(); 110 | } catch (IOException e) { 111 | e.printStackTrace(); 112 | } 113 | } 114 | 115 | 116 | 117 | } 118 | 119 | /** 120 | * 滚动对比 121 | * 122 | * @param srcMap 123 | * @param blk 124 | * @return 125 | */ 126 | private int checkBlk(Map srcMap, int offset, List difList) { 127 | int start = offset; 128 | BlockChecksums bck = null; // 新老文件相同的块,老文件的 129 | BlockChecksums blk = null; // 新文件取出的块 130 | for (; start < updateFile.length(); start++) { 131 | blk = getNextBlock(start); 132 | if (srcMap.containsKey(blk.getWeakChecksum())) { 133 | bck = srcMap.get(blk.getWeakChecksum()); 134 | if (bck.getHexStrongChecksum().equals(blk.getHexStrongChecksum())) { 135 | break; 136 | } 137 | } 138 | } 139 | 140 | if (blk != null) { 141 | int len = start - offset; 142 | 143 | if (len > 0) { 144 | try { 145 | if (diffraf == null) { 146 | diffraf = new RandomAccessFile(updateFile, "r"); 147 | } 148 | byte[] by = new byte[len]; 149 | diffraf.seek(offset); 150 | diffraf.read(by, 0, len); 151 | 152 | DiffCheckItem dl = new DiffCheckItem(); 153 | dl.setMatch(false); 154 | dl.setData(by); 155 | difList.add(dl); 156 | } catch (FileNotFoundException e) { 157 | e.printStackTrace(); 158 | } catch (IOException e) { 159 | e.printStackTrace(); 160 | } 161 | 162 | if (bck != null) { 163 | DiffCheckItem dl = new DiffCheckItem(); 164 | dl.setIndex(bck.getIndex()); 165 | dl.setMatch(true); 166 | difList.add(dl); 167 | } 168 | } else { 169 | DiffCheckItem dl = new DiffCheckItem(); 170 | dl.setIndex(bck.getIndex()); 171 | dl.setMatch(true); 172 | difList.add(dl); 173 | } 174 | return start + Constants.BLOCK_SIZE; 175 | } else { 176 | return start; 177 | } 178 | } 179 | 180 | /** 181 | * 根据文件偏移量获取每一块的checksum 182 | * 183 | * @param offset 184 | * @return BlockChecksums 185 | */ 186 | private BlockChecksums getNextBlock(int offset) { 187 | byte[] buf = new byte[Constants.BLOCK_SIZE]; 188 | try { 189 | if (raf == null) { 190 | raf = new RandomAccessFile(updateFile, "r"); 191 | } 192 | raf.seek(offset); 193 | int re = raf.read(buf, 0, Constants.BLOCK_SIZE); 194 | BlockChecksums blk = new BlockChecksums(buf, offset, re); 195 | return blk; 196 | } catch (FileNotFoundException e) { 197 | e.printStackTrace(); 198 | } catch (IOException e) { 199 | e.printStackTrace(); 200 | } 201 | return null; 202 | } 203 | 204 | public FileChecksums getSrcFile() { 205 | return srcFile; 206 | } 207 | 208 | public void setSrcFile(FileChecksums srcFile) { 209 | this.srcFile = srcFile; 210 | } 211 | 212 | public File getUpdateFile() { 213 | return updateFile; 214 | } 215 | 216 | public void setUpdateFile(File updateFile) { 217 | this.updateFile = updateFile; 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/util/ByteTool.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.util; 2 | 3 | import java.io.OutputStream; 4 | 5 | /** 6 | * 将字符,数字 与byte 互相转化工具 7 | * @author jiuyuehe 8 | * 9 | */ 10 | public class ByteTool { 11 | 12 | /** 13 | * 读取byte [] 14 | * 15 | * @param b 16 | * @param target 17 | * @throws Exception 18 | */ 19 | public static void addByte(byte[] b, OutputStream os, int len) 20 | throws Exception { 21 | if (b.length > len) { 22 | os.write(b, 0, len); 23 | } else { 24 | int l = len - (b.length - 1); 25 | 26 | while (--l > 0) { 27 | os.write(0); 28 | } 29 | os.write(b, 0, b.length); 30 | } 31 | } 32 | 33 | /** 34 | * int to byte[] 最多4个字节 35 | * 36 | * @param i 37 | * @param len 38 | * @return 39 | */ 40 | public static byte[] intToByte(int i, int len) { 41 | byte[] abyte = new byte[len]; 42 | for (int j = 0; j < abyte.length; j++) { 43 | abyte[j] = (byte) ((i >>> ((len-j-1)*8)) & 0xff ); 44 | } 45 | return abyte; 46 | } 47 | 48 | /** 49 | * 最多支持4字节 50 | * @param bytes 51 | * @return 52 | */ 53 | public static int bytesToInt(byte[] bytes) { 54 | int addr = 0; 55 | int len = bytes.length; 56 | for (int j = 0; j < len; j++) { 57 | addr = (addr << 8) | (bytes[j] & 0xff); 58 | } 59 | return addr; 60 | } 61 | 62 | public static void main(String[] args) { 63 | 64 | 65 | int a = bytesToInt( intToByte(1,2)); 66 | 67 | System.out.println(a); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/util/Constants.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.util; 2 | 3 | public class Constants { 4 | /** 5 | * 分块大小 6 | */ 7 | public static int BLOCK_SIZE = 1024*5; 8 | public static final String MD5 = "MD5"; 9 | 10 | public static int getBLOCK_SIZE() { 11 | return BLOCK_SIZE; 12 | } 13 | public static void setBLOCK_SIZE(int bLOCK_SIZE) { 14 | BLOCK_SIZE = bLOCK_SIZE; 15 | } 16 | 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/util/QuickMD5.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.util; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.nio.MappedByteBuffer; 8 | import java.nio.channels.FileChannel; 9 | import java.security.MessageDigest; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class QuickMD5 { 15 | 16 | protected static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', 17 | '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 18 | protected static MessageDigest messagedigest = null; 19 | static { 20 | try { 21 | messagedigest = MessageDigest.getInstance("MD5"); 22 | } catch (NoSuchAlgorithmException nsaex) { 23 | nsaex.printStackTrace(); 24 | } 25 | } 26 | 27 | public static String getFileMD5Channel(File file) throws IOException { 28 | FileInputStream in = new FileInputStream(file); 29 | FileChannel ch = in.getChannel(); 30 | MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, 31 | file.length()); 32 | messagedigest.update(byteBuffer); 33 | return bufferToHex(messagedigest.digest()); 34 | } 35 | 36 | public static String getFileMD5Buffer(File file) throws IOException{ 37 | byte [] by = new byte[8192*2]; 38 | FileInputStream in = new FileInputStream(file); 39 | int length; 40 | while ((length = in.read(by)) != -1) { 41 | messagedigest.update(by, 0, length); 42 | } 43 | return bufferToHex(messagedigest.digest()); 44 | } 45 | /** 46 | * get file md5 list 47 | * @param file 48 | * @return 49 | * @throws IOException 50 | */ 51 | public static List getFileMD5Buffer(File file ,int size) throws IOException{ 52 | List md5List = new ArrayList(); 53 | byte [] by = new byte[size]; 54 | FileInputStream in = new FileInputStream(file); 55 | int length; 56 | while ((length = in.read(by)) != -1) { 57 | messagedigest.update(by, 0, length); 58 | md5List.add(bufferToHex(messagedigest.digest())); 59 | } 60 | in.close(); 61 | return md5List; 62 | //return bufferToHex(messagedigest.digest()); 63 | } 64 | 65 | /** 66 | * get file md5 list 67 | * @param file 68 | * @return 69 | * @throws IOException 70 | */ 71 | public static List getReverseFileMD5Buffer(File file ,int size) throws IOException{ 72 | List md5List = new ArrayList(); 73 | byte [] by = new byte[size]; 74 | FileInputStream in = new FileInputStream(file); 75 | int length; 76 | while ((length = in.read(by)) != -1) { 77 | messagedigest.update(by, 0, length); 78 | md5List.add(bufferToHex(messagedigest.digest())); 79 | } 80 | in.close(); 81 | return md5List; 82 | //return bufferToHex(messagedigest.digest()); 83 | } 84 | 85 | public static String getMD5String(String s) { 86 | return getMD5String(s.getBytes()); 87 | } 88 | 89 | public static String getMD5String(byte[] bytes) { 90 | messagedigest.update(bytes); 91 | return bufferToHex(messagedigest.digest()); 92 | } 93 | 94 | private static String bufferToHex(byte bytes[]) { 95 | return bufferToHex(bytes, 0, bytes.length); 96 | } 97 | 98 | private static String bufferToHex(byte bytes[], int m, int n) { 99 | StringBuffer stringbuffer = new StringBuffer(2 * n); 100 | int k = m + n; 101 | for (int l = m; l < k; l++) { 102 | appendHexPair(bytes[l], stringbuffer); 103 | } 104 | return stringbuffer.toString(); 105 | } 106 | 107 | private static void appendHexPair(byte bt, StringBuffer stringbuffer) { 108 | char c0 = hexDigits[(bt & 0xf0) >> 4]; 109 | char c1 = hexDigits[bt & 0xf]; 110 | stringbuffer.append(c0); 111 | stringbuffer.append(c1); 112 | } 113 | 114 | public static void main(String[] args) throws IOException { 115 | File big = new File( 116 | "E:\\tools\\VMware-workstation-full-9.0.2-1031769.exe"); 117 | File small = new File( 118 | "C:\\Users\\jiuyuehe\\Desktop\\other\\addtest\\word1.docx"); 119 | // long begin = System.currentTimeMillis(); 120 | // String md5 = getFileMD5Channel(big); 121 | // long end = System.currentTimeMillis(); 122 | // System.out.println("md5:" + md5 + " time:" + ((end - begin)) + "ms"); 123 | // 124 | // String md52 = getFileMD5Buffer(big); 125 | // long t = System.currentTimeMillis(); 126 | // System.out.println("md52:" + md52 + " time:" + ((t - end)) + "ms"); 127 | 128 | String md5 = getFileMD5Channel(small); 129 | System.out.println("md5:"+ md5); 130 | List md5List = getFileMD5Buffer(small,512); 131 | System.out.println(md5List.size()); 132 | System.out.println(md5List); 133 | 134 | 135 | RandomAccessFile randomFile = new RandomAccessFile(small, "r"); 136 | byte [] bys = new byte[1024*1024]; 137 | randomFile.seek(4); 138 | //randomFile.write(ByteTool.intToByte(startIndex, 4)); 139 | randomFile.read(bys, 0, 1024*1024); 140 | 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/util/RsyncException.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.util; 2 | 3 | public class RsyncException extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = -2331722209210513479L; 9 | 10 | public RsyncException() { 11 | 12 | } 13 | 14 | public RsyncException(Throwable throwable) { 15 | initCause(throwable); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/rysnc/util/RsyncFileUtils.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.rysnc.util; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.RandomAccessFile; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.apache.log4j.Logger; 12 | 13 | import com.laz.filesync.rysnc.checksums.DiffCheckItem; 14 | import com.laz.filesync.rysnc.checksums.DiffFileMeta; 15 | 16 | public class RsyncFileUtils { 17 | private static final Logger logger = Logger.getLogger(RsyncFileUtils.class); 18 | private static int SAME = 0; 19 | private static int DIFF = 1; 20 | 21 | /** 22 | * 生成临时上传文件 23 | * @param dciList 24 | * 0: 同样的块 25 | * 1: 不同的块 26 | * @throws Exception 27 | */ 28 | public static void createRsyncFile(List dciList, File tmpFile, int blockSize) throws Exception{ 29 | 30 | try { 31 | FileOutputStream fos = new FileOutputStream(tmpFile); 32 | 33 | fos.write(ByteTool.intToByte(blockSize, 4)); 34 | 35 | 36 | for (DiffCheckItem diffCheckItem : dciList) { 37 | if(diffCheckItem.isMatch()){ 38 | 39 | fos.write(ByteTool.intToByte(SAME, 1), 0, 1); 40 | 41 | //System.out.println("block-"+diffCheckItem.getIndex()); 42 | 43 | fos.write(ByteTool.intToByte((int)diffCheckItem.getIndex(), 2), 0, 2); 44 | 45 | }else{ 46 | fos.write(ByteTool.intToByte(DIFF, 1), 0, 1); 47 | 48 | int length = diffCheckItem.getData().length; 49 | 50 | fos.write(ByteTool.intToByte(length, 2), 0, 2); 51 | 52 | fos.write(diffCheckItem.getData(), 0, length); 53 | 54 | } 55 | } 56 | 57 | fos.close(); 58 | } catch (FileNotFoundException e) { 59 | e.printStackTrace(); 60 | } 61 | 62 | 63 | } 64 | 65 | /** 66 | * 合并上传临时文件 67 | * @param srcFile 68 | * @param newFile 69 | * @param rsyncFile 70 | * @throws IOException 71 | */ 72 | public static void combineRsyncFile(File srcFile, File newFile,File rsyncFile) throws IOException{ 73 | DiffFileMeta dfm = tmp2Item(rsyncFile); 74 | int blockSize = dfm.getBlockSize(); 75 | FileOutputStream fos = new FileOutputStream(newFile); 76 | RandomAccessFile srcraf = new RandomAccessFile(srcFile, "r"); 77 | //logger.info("合并文件"+srcFile.getAbsolutePath()); 78 | for (DiffCheckItem item : dfm.getDiffList()) { 79 | if(item.isMatch()){ 80 | long i = item.getIndex(); 81 | srcraf.seek(i*blockSize); 82 | byte [] by = new byte[blockSize]; 83 | //写入对应长度,防止最后一次循环长度不够,多写入问题 84 | int len = srcraf.read(by); 85 | if (len > 0) { 86 | fos.write(by,0,len); 87 | } 88 | }else{ 89 | byte [] difby = item.getData(); 90 | fos.write(difby); 91 | } 92 | } 93 | 94 | srcraf.close(); 95 | 96 | fos.close(); 97 | 98 | 99 | } 100 | 101 | 102 | /** 103 | * 将上传的文件转化为对象 104 | * @param tmpFile 105 | * @return 106 | * @throws IOException 107 | */ 108 | private static DiffFileMeta tmp2Item(File tmpFile) throws IOException { 109 | 110 | RandomAccessFile diffraf = new RandomAccessFile(tmpFile, "r"); 111 | 112 | DiffFileMeta dfm = new DiffFileMeta(); 113 | 114 | int start = 0; 115 | 116 | int blockSize = getBlockSize(diffraf ,4); 117 | 118 | dfm.setBlockSize(blockSize); 119 | 120 | //dfm.setDiffList(diffList); 121 | 122 | List difList = new ArrayList(); 123 | 124 | start +=4; 125 | 126 | do{ 127 | start = readBuf(diffraf, start,difList); 128 | 129 | }while(start < tmpFile.length()); 130 | 131 | dfm.setDiffList(difList); 132 | 133 | return dfm; 134 | } 135 | 136 | /** 137 | * 138 | * @param diffraf 139 | * @param size 140 | * @param offset 141 | * @return 142 | * @throws IOException 143 | */ 144 | private static int readBuf(RandomAccessFile diffraf , int offset,List difList) throws IOException { 145 | 146 | int index = offset; 147 | byte[] matchby = new byte[1]; 148 | //diffraf.seek(index); 149 | diffraf.read(matchby); 150 | int match = ByteTool.bytesToInt(matchby); 151 | 152 | byte[] sizeby = new byte[2]; 153 | //diffraf.seek(index+1); 154 | diffraf.read(sizeby); 155 | if(match ==0){ 156 | int blockindex = ByteTool.bytesToInt(sizeby); 157 | //System.out.println("match "+blockindex); 158 | 159 | DiffCheckItem di= new DiffCheckItem(); 160 | di.setMatch(true); 161 | di.setIndex(blockindex); 162 | 163 | difList.add(di); 164 | 165 | return index +3; 166 | 167 | }else{ 168 | int dataSize = ByteTool.bytesToInt(sizeby); 169 | // System.out.println("dataSize"+dataSize); 170 | byte [] by = new byte[dataSize]; 171 | 172 | diffraf.read(by); 173 | 174 | DiffCheckItem di= new DiffCheckItem(); 175 | di.setMatch(false); 176 | di.setData(by); 177 | 178 | difList.add(di); 179 | 180 | return index+3+dataSize; 181 | } 182 | 183 | } 184 | 185 | /** 186 | * 获取分块大小 187 | * @param diffraf 188 | * @param i 189 | * @return 190 | * @throws IOException 191 | */ 192 | private static int getBlockSize(RandomAccessFile diffraf, int i) throws IOException { 193 | 194 | byte[] by = new byte[4]; 195 | 196 | diffraf.read(by); 197 | 198 | return ByteTool.bytesToInt(by); 199 | } 200 | 201 | /** 202 | * 203 | * @param file1 204 | * @param file2 205 | * @return 206 | * @throws IOException 207 | */ 208 | public static boolean checkFileSame(File file1,File file2) throws IOException{ 209 | String m1 = QuickMD5.getFileMD5Buffer(file1); 210 | String m2 = QuickMD5.getFileMD5Buffer(file2); 211 | if(m1.equals(m2)) 212 | return true; 213 | else 214 | return false; 215 | } 216 | 217 | 218 | 219 | public static void main(String[] args) throws IOException { 220 | 221 | // String basePath = System.getProperty("user.dir"); 222 | // 223 | // File rsyncFile = new File(basePath + "/src/test/resources/" + "tmp"); 224 | // File srcFile = new File(basePath + "/src/test/resources/" + "lorem.txt"); 225 | // File newFile = new File(basePath + "/src/test/resources/" + "lorem-new.txt"); 226 | // File tarFile = new File(basePath + "/src/test/resources/" + "lorem2.txt"); 227 | // 228 | // 229 | // RsyncFileUtils ru = new RsyncFileUtils(); 230 | //// try { 231 | //// ru.tmp2Item(tmp); 232 | //// } catch (IOException e) { 233 | //// // TODO Auto-generated catch block 234 | //// e.printStackTrace(); 235 | //// } 236 | // //ru.combineRsyncFile(srcFile, newFile, rsyncFile); 237 | // 238 | // System.out.println(RsyncFileUtils.checkFileSame(newFile,tarFile)); 239 | //tmp2Item(new File("C:\\Users\\lz578\\Desktop\\desktop3.jpg")); 240 | byte[] r = ByteTool.intToByte(Constants.BLOCK_SIZE, 4); 241 | 242 | System.out.println(ByteTool.bytesToInt(r)); 243 | } 244 | 245 | 246 | 247 | } 248 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/server/FileReceiveServer.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.server; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.laz.filesync.server.file.handler.FileReceiveServerHandler; 7 | 8 | import io.netty.bootstrap.ServerBootstrap; 9 | import io.netty.channel.ChannelFuture; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelPipeline; 12 | import io.netty.channel.EventLoopGroup; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.SocketChannel; 15 | import io.netty.channel.socket.nio.NioServerSocketChannel; 16 | 17 | public class FileReceiveServer { 18 | private int port; 19 | private static Logger logger = LoggerFactory.getLogger(FileReceiveServer.class); 20 | public FileReceiveServer(int port) { 21 | this.port = port; 22 | } 23 | public void start() { 24 | EventLoopGroup bossGroup = new NioEventLoopGroup();// 处理连接线程组 25 | EventLoopGroup workGroup = new NioEventLoopGroup();// 处理io线程组 26 | 27 | ServerBootstrap server = new ServerBootstrap(); 28 | server.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)// 指定处理客户端的通道 29 | .childHandler(new ChannelInitializer() { 30 | @Override 31 | protected void initChannel(SocketChannel ch) throws Exception { 32 | ChannelPipeline pipeline = ch.pipeline(); 33 | pipeline.addLast(new FileReceiveServerHandler()); 34 | 35 | } 36 | });// 通道初始化 37 | try { 38 | logger.info("---------------------文件传输端口启动--------------------"); 39 | ChannelFuture future = server.bind(port).sync(); 40 | future.sync(); 41 | } catch (InterruptedException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | public static void main(String[] args) { 47 | new FileReceiveServer(8990).start(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/server/FileSyncServer.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.server; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import com.laz.filesync.conf.Configuration; 9 | import com.laz.filesync.server.file.handler.FileReceiveServerHandler; 10 | import com.laz.filesync.server.handler.MsgServerHandler; 11 | import com.laz.filesync.util.Constants; 12 | 13 | import io.netty.bootstrap.ServerBootstrap; 14 | import io.netty.channel.ChannelFuture; 15 | import io.netty.channel.ChannelInitializer; 16 | import io.netty.channel.ChannelPipeline; 17 | import io.netty.channel.EventLoopGroup; 18 | import io.netty.channel.nio.NioEventLoopGroup; 19 | import io.netty.channel.socket.SocketChannel; 20 | import io.netty.channel.socket.nio.NioServerSocketChannel; 21 | import io.netty.handler.codec.http.HttpObjectAggregator; 22 | import io.netty.handler.codec.http.HttpServerCodec; 23 | import io.netty.handler.codec.serialization.ClassResolvers; 24 | import io.netty.handler.codec.serialization.ObjectDecoder; 25 | import io.netty.handler.codec.serialization.ObjectEncoder; 26 | import io.netty.handler.stream.ChunkedWriteHandler; 27 | import io.netty.util.concurrent.DefaultEventExecutorGroup; 28 | import io.netty.util.concurrent.EventExecutorGroup; 29 | 30 | /** 31 | * 文件同步服务端 32 | * 33 | * 34 | */ 35 | public class FileSyncServer { 36 | private static Logger logger = LoggerFactory.getLogger(FileSyncServer.class); 37 | private int port; 38 | private int filePort; 39 | private Configuration conf; 40 | 41 | public FileSyncServer(Configuration conf) { 42 | this.conf = conf; 43 | init(); 44 | } 45 | 46 | private void init() { 47 | this.port = conf.getPort() == 0 ? 8989 : conf.getPort(); 48 | this.filePort = conf.getFilePort() == 0 ? 8990 : conf.getFilePort(); 49 | } 50 | 51 | public void start() { 52 | EventLoopGroup bossGroup = new NioEventLoopGroup();// 处理连接线程组 53 | EventLoopGroup workGroup = new NioEventLoopGroup();// 处理io线程组 54 | 55 | ServerBootstrap server = new ServerBootstrap(); 56 | server.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)// 指定处理客户端的通道 57 | .childHandler(new ChannelInitializer() { 58 | @Override 59 | protected void initChannel(SocketChannel ch) throws Exception { 60 | ChannelPipeline pipeline = ch.pipeline(); 61 | pipeline.addLast("decoder", new ObjectDecoder(Constants.OBJECT_SIZE_LIMIT, 62 | ClassResolvers.cacheDisabled(this.getClass().getClassLoader()))); 63 | pipeline.addLast("encoder", new ObjectEncoder()); 64 | MsgServerHandler handler = new MsgServerHandler(); 65 | pipeline.addLast("handler", handler); 66 | } 67 | });// 通道初始化 68 | try { 69 | logger.info("---------------------服务端启动--------------------"); 70 | ChannelFuture future = server.bind(port).sync(); 71 | startFileServer(); 72 | future.channel().closeFuture().sync(); 73 | } catch (InterruptedException e) { 74 | e.printStackTrace(); 75 | } finally { 76 | bossGroup.shutdownGracefully(); 77 | workGroup.shutdownGracefully(); 78 | } 79 | } 80 | 81 | private void startFileServer() throws InterruptedException { 82 | new FileReceiveServer(filePort).start(); 83 | } 84 | 85 | public static void main(String[] args) { 86 | Configuration conf = new Configuration(); 87 | conf.setServerIP("127.0.0.1"); 88 | new FileSyncServer(conf).start(); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/server/file/handler/FileReceiveServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.server.file.handler; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.RandomAccessFile; 6 | import java.util.Arrays; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import com.laz.filesync.server.FileSyncServer; 12 | import com.laz.filesync.util.Coder; 13 | import com.laz.filesync.util.FileSyncUtil; 14 | import com.laz.filesync.util.FileUtil; 15 | 16 | import io.netty.buffer.ByteBuf; 17 | import io.netty.channel.ChannelHandlerContext; 18 | import io.netty.channel.ChannelInboundHandlerAdapter; 19 | 20 | public class FileReceiveServerHandler extends ChannelInboundHandlerAdapter { 21 | private static Logger logger = LoggerFactory.getLogger(FileReceiveServerHandler.class); 22 | private long start; 23 | @Override 24 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 25 | super.channelActive(ctx); 26 | System.out.println("----------------active-------------"); 27 | start = 0; 28 | } 29 | private long fileLen; 30 | private String fileName; 31 | @Override 32 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 33 | ByteBuf byteBuf = (ByteBuf) msg; 34 | if (FileSyncUtil.STAERT_FLAG == byteBuf.getInt(0)) { 35 | byteBuf.readInt(); 36 | int nameLen = byteBuf.readInt(); 37 | byte[] b = new byte[nameLen]; 38 | byteBuf.readBytes(b); 39 | fileName = new String(b); 40 | fileLen = byteBuf.readLong(); 41 | logger.info(fileLen+" "+new String(b)); 42 | logger.info("fileName"+fileName); 43 | } 44 | if (fileName==null) { 45 | logger.error("无法获取同步包文件名"+fileName); 46 | return ; 47 | } 48 | File file = FileSyncUtil.getServerTempFile(fileName); 49 | RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); 50 | randomAccessFile.seek(start);// 移动文件记录指针的位置, 51 | int length = byteBuf.readableBytes(); 52 | start += length; 53 | byte[] bytes = new byte[length]; 54 | byteBuf.readBytes(bytes); 55 | randomAccessFile.write(bytes);// 调用了seek(start)方法,是指把文件的记录指针定位到start字节的位置。也就是说程序将从start字节开始写数据 56 | byteBuf.release(); 57 | randomAccessFile.close(); 58 | if (start>=fileLen) { 59 | logger.info(file.getAbsolutePath()+"文件接收完成"); 60 | String digest = Coder.encryptBASE64(FileSyncUtil.generateFileDigest(file)); 61 | FileUtil.countDown(digest); 62 | ctx.close(); 63 | } 64 | } 65 | 66 | 67 | 68 | @Override 69 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 70 | super.channelInactive(ctx); 71 | System.out.println("----------------channelInactive-------------"); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/server/file/handler/FileSendClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.server.file.handler; 2 | 3 | import java.io.File; 4 | import java.util.Arrays; 5 | 6 | import com.laz.filesync.server.msg.FileInfo; 7 | 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.ChannelInboundHandlerAdapter; 10 | import io.netty.channel.DefaultFileRegion; 11 | 12 | public class FileSendClientHandler extends ChannelInboundHandlerAdapter { 13 | 14 | @Override 15 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 16 | super.channelRead(ctx, msg); 17 | } 18 | @Override 19 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 20 | File file = new File("d:/word/word.zip"); 21 | writeAndFlushFileRegion(ctx, file); 22 | } 23 | 24 | private void writeAndFlushFileRegion(ChannelHandlerContext ctx, File file) { 25 | DefaultFileRegion fileRegion = new DefaultFileRegion(file, 0, file.length()); 26 | FileInfo info = new FileInfo(); 27 | info.setFilename(file.getName()); 28 | info.setLength(file.length()); 29 | ctx.writeAndFlush(info); 30 | ctx.writeAndFlush(fileRegion).addListener(future -> { 31 | if (future.isSuccess()) { 32 | System.out.println("发送完成..."); 33 | } 34 | }); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/server/handler/MsgServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.server.handler; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import java.util.concurrent.CountDownLatch; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import org.apache.commons.io.FileUtils; 16 | import org.apache.commons.io.IOUtils; 17 | import org.apache.log4j.Logger; 18 | 19 | import com.laz.filesync.client.msg.DiffFilesSyncMsg; 20 | import com.laz.filesync.client.msg.RequestMsg; 21 | import com.laz.filesync.conf.Configuration; 22 | import com.laz.filesync.msg.BaseMsg; 23 | import com.laz.filesync.msg.ErrorMsg; 24 | import com.laz.filesync.rysnc.checksums.FileChecksums; 25 | import com.laz.filesync.rysnc.util.RsyncFileUtils; 26 | import com.laz.filesync.server.msg.FileCheckSumsMsg; 27 | import com.laz.filesync.util.Coder; 28 | import com.laz.filesync.util.FileSyncUtil; 29 | import com.laz.filesync.util.FileUtil; 30 | import com.laz.filesync.util.PathMap; 31 | import com.laz.filesync.util.ZipUtils; 32 | 33 | import io.netty.channel.ChannelHandlerContext; 34 | import io.netty.channel.SimpleChannelInboundHandler; 35 | 36 | public class MsgServerHandler extends SimpleChannelInboundHandler { 37 | public static final Logger logger = Logger.getLogger(MsgServerHandler.class); 38 | 39 | @Override 40 | protected void channelRead0(ChannelHandlerContext ctx, BaseMsg msg) throws Exception { 41 | switch (msg.getType()) { 42 | case REQUEST: { 43 | RequestMsg requestMsg = (RequestMsg) msg; 44 | BaseMsg response = dealRequestMsg(requestMsg); 45 | ctx.writeAndFlush(response); 46 | } 47 | break; 48 | case CHECK_SUM: { 49 | 50 | } 51 | break; 52 | case SYNC: { 53 | DiffFilesSyncMsg diffMsg = (DiffFilesSyncMsg) msg; 54 | try { 55 | CountDownLatch latch = FileUtil.newCount(diffMsg.getFileDigest()); 56 | if (latch != null) { 57 | logger.info("等待压缩包接受完成"); 58 | latch.await(60 * 5, TimeUnit.SECONDS); 59 | logger.info("压缩包接受完成"); 60 | } 61 | combineRsyncFile(diffMsg); 62 | logger.info("文件同步完成,发送服务端进行同步结果验证"); 63 | BaseMsg m = getCheckSumsMsg(new File(diffMsg.getServerPath())); 64 | ctx.writeAndFlush(m); 65 | } catch (Exception e) { 66 | logger.error(ctx.channel().remoteAddress() + "文件同步失败,请客服端重新尝试" + e.getMessage()); 67 | e.printStackTrace(); 68 | } 69 | } 70 | break; 71 | default: 72 | break; 73 | 74 | } 75 | 76 | } 77 | 78 | private void combineRsyncFile(DiffFilesSyncMsg diffMsg) throws Exception { 79 | // 进行文件包完整性验证 80 | File serverFile = FileSyncUtil.getServerTempFile(diffMsg.getFileName()); 81 | 82 | boolean v = verify(serverFile, diffMsg.getFileDigest()); 83 | if (v) { 84 | logger.info("diff包文件完整性校验一致"); 85 | logger.info("文件解压开始--------------"); 86 | long start = System.currentTimeMillis(); 87 | String fileName = serverFile.getName().replace(".zip", ""); 88 | String unzipPath = System.getProperty("java.io.tmpdir") + File.separator + fileName; 89 | ZipUtils.unzipFile(serverFile, unzipPath); 90 | String filepath = unzipPath + File.separator + fileName.substring(0, fileName.indexOf("_server")); 91 | long end = System.currentTimeMillis(); 92 | logger.info("文件解压结束--------------" + (end - start) + "ms"); 93 | // 遍历文件夹,获取同步文件元数据集合信息(key 文件相对路径,文件实际路径) 94 | Map pathMap = new HashMap(); 95 | Map typeMap = new HashMap(); 96 | getFileMap(filepath, filepath, pathMap, typeMap); 97 | // 进行存在的文件合并 98 | List exists = new ArrayList(); 99 | combineExistsFile(new File(diffMsg.getServerPath()), diffMsg.getServerPath(), pathMap, exists, 100 | diffMsg.getNoSynsFileSets()); 101 | end = System.currentTimeMillis(); 102 | logger.info("完成存在的文件合并" + (end - start) + "ms"); 103 | // 没有找到的文件或文件夹进行新建 104 | newCreateFile(typeMap, filepath, diffMsg.getServerPath(), exists); 105 | end = System.currentTimeMillis(); 106 | logger.info("完成不存在的文件新建" + (end - start) + "ms"); 107 | logger.info("----------------完成文件同步------------------" + (end - start) + "ms"); 108 | } else { 109 | logger.error("文件完整性校验不一致"); 110 | } 111 | } 112 | 113 | private void newCreateFile(Map typeMap, String srcPath, String desPath, List exists) 114 | throws IOException { 115 | for (String k : typeMap.keySet()) { 116 | if (!exists.contains(k)) { 117 | File f = new File(desPath + File.separator + k); 118 | logger.debug("新建" + f.getAbsolutePath()); 119 | // 判断文件类型 120 | if (typeMap.get(k)) { 121 | f.mkdirs(); 122 | } else { 123 | FileUtil.createFile(f); 124 | FileInputStream in = new FileInputStream(new File(srcPath + File.separator + k)); 125 | FileOutputStream out = new FileOutputStream(f); 126 | IOUtils.copy(in, out); 127 | IOUtils.closeQuietly(in); 128 | IOUtils.closeQuietly(out); 129 | } 130 | } 131 | } 132 | } 133 | 134 | private void combineExistsFile(File f, String serverPath, Map map, List exists, 135 | Set noSynsFileSets) throws IOException { 136 | if (serverPath == null) { 137 | return; 138 | } 139 | String p = FileUtil.getRelativePath(f, serverPath); 140 | if (noSynsFileSets.contains(FileUtil.convertPath(p))) { 141 | exists.add(p); 142 | // 不需要同步的文件 143 | return; 144 | } 145 | if (f.isDirectory()) { 146 | if (f.listFiles().length == 0) { 147 | // 空目录删除 148 | String path = map.get(FileUtil.getRelativePath(f, serverPath)); 149 | if (path == null) { 150 | f.delete(); 151 | } 152 | } else { 153 | for (File file : f.listFiles()) { 154 | combineExistsFile(file, serverPath, map, exists, noSynsFileSets); 155 | } 156 | } 157 | } else { 158 | String rynscFilePath = map.get(FileUtil.getRelativePath(f, serverPath)); 159 | boolean exist = false; 160 | if (rynscFilePath != null) { 161 | File rsyncFile = new File(rynscFilePath); 162 | if (rsyncFile.isFile()) { 163 | String newFilePath = f.getAbsolutePath() + FileSyncUtil.NEW_FILE_FLAG; 164 | File newFile = new File(newFilePath); 165 | logger.debug("合并" + f.getAbsolutePath()); 166 | RsyncFileUtils.combineRsyncFile(f, newFile, rsyncFile); 167 | boolean flag = f.delete(); 168 | if (flag) { 169 | newFile.renameTo(f); 170 | } else { 171 | logger.error(f.getAbsoluteFile() + "文件不能被删除,检查是否文件被占用或者流未关闭"); 172 | throw new RuntimeException(f.getAbsoluteFile() + "文件不能被删除,检查是否文件被占用或者流未关闭"); 173 | } 174 | exists.add(FileUtil.getRelativePath(f, serverPath)); 175 | exist = true; 176 | } 177 | } 178 | // 不存在进行删除 179 | if (!exist) { 180 | logger.debug("删除" + f.getAbsolutePath()); 181 | if (!f.delete()) { 182 | logger.error(f.getAbsoluteFile() + "文件不能被删除,检查是否文件被占用或者流未关闭"); 183 | // throw new RuntimeException(f.getAbsoluteFile()+"文件不能被删除,检查是否文件被占用或者流未关闭"); 184 | } 185 | } 186 | } 187 | 188 | } 189 | 190 | public static void main(String[] args) { 191 | File f = new File("D:\\filesync\\server\\1.txt_new_rsync_file74565"); 192 | f.renameTo(new File("D:\\\\filesync\\\\server\\\\1.txt")); 193 | } 194 | 195 | private void getFileMap(String filepath, String relative, Map pathMap, 196 | Map typeMap) { 197 | File f = new File(filepath); 198 | String relativepath = FileUtil.getRelativePath(f, relative); 199 | pathMap.put(relativepath, f.getAbsolutePath()); 200 | if (f.isDirectory()) { 201 | typeMap.put(relativepath, true); 202 | for (File file : f.listFiles()) { 203 | getFileMap(file.getAbsolutePath(), relative, pathMap, typeMap); 204 | } 205 | } else { 206 | typeMap.put(relativepath, false); 207 | } 208 | } 209 | 210 | private boolean verify(File serverFile, String fileDigest) throws Exception { 211 | logger.info("接受的文件检验码:" + fileDigest); 212 | String digest1 = Coder.encryptBASE64(FileSyncUtil.generateFileDigest(serverFile)); 213 | logger.info(serverFile.getAbsolutePath() + "检验码:" + digest1); 214 | if (digest1.equals(fileDigest)) { 215 | return true; 216 | } 217 | return false; 218 | } 219 | 220 | private BaseMsg dealRequestMsg(RequestMsg requestMsg) { 221 | BaseMsg msg = null; 222 | // 获取客服端相应同步的服务端目录 223 | String folderPath = requestMsg.getFolderPath(); 224 | File folder = new File(folderPath); 225 | if (!folder.exists()) { 226 | folder.mkdirs(); 227 | } 228 | // 必须是目录 229 | if (folder.isDirectory()) { 230 | msg = getCheckSumsMsg(folder); 231 | } else { 232 | ErrorMsg errorMsg = new ErrorMsg(); 233 | errorMsg.setCode(ErrorMsg.Code.SERVER_PATH_IS_NOT_FOLDER.getCode()); 234 | errorMsg.setMsg(ErrorMsg.Code.SERVER_PATH_IS_NOT_FOLDER.getMsg()); 235 | msg = errorMsg; 236 | } 237 | return msg; 238 | } 239 | 240 | private BaseMsg getCheckSumsMsg(File folder) { 241 | Map checksums = new PathMap(); 242 | FileSyncUtil.getFileCheckSumsAndBlockSums(folder, folder, checksums); 243 | logger.info(checksums.size() + "----------"); 244 | FileCheckSumsMsg checksumsMsg = new FileCheckSumsMsg(); 245 | checksumsMsg.setChecksumsMap(checksums); 246 | return checksumsMsg; 247 | 248 | } 249 | 250 | /* 251 | * 客户端连接到服务器 252 | */ 253 | @Override 254 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 255 | super.channelActive(ctx); 256 | } 257 | 258 | /** 259 | * 异常错误处理 260 | */ 261 | @Override 262 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 263 | cause.printStackTrace(); 264 | logger.error("错误原因:" + cause.getMessage()); 265 | ctx.channel().close(); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/server/msg/FileCheckSumsMsg.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.server.msg; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.laz.filesync.msg.BaseMsg; 8 | import com.laz.filesync.msg.MsgType; 9 | import com.laz.filesync.rysnc.checksums.FileChecksums; 10 | 11 | /** 12 | * 13 | * 服务器文件检验和结果 14 | * 15 | */ 16 | public class FileCheckSumsMsg extends BaseMsg{ 17 | private static final long serialVersionUID = 1L; 18 | //检验和集 19 | private Map checksumsMap; 20 | 21 | public Map getChecksumsMap() { 22 | return checksumsMap; 23 | } 24 | 25 | public void setChecksumsMap(Map checksumsMap) { 26 | this.checksumsMap = checksumsMap; 27 | } 28 | 29 | @Override 30 | public MsgType getType() { 31 | return MsgType.CHECK_SUM; 32 | } 33 | 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/server/msg/FileInfo.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.server.msg; 2 | 3 | public class FileInfo { 4 | private String filename; 5 | private long length; 6 | 7 | public void setFilename(String filename) { 8 | this.filename = filename; 9 | } 10 | 11 | public String getFilename() { 12 | return filename; 13 | } 14 | public void setLength(long length) { 15 | this.length = length; 16 | } 17 | 18 | public long getLength() { 19 | return length; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/util/Coder.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.util; 2 | 3 | 4 | import java.security.MessageDigest; 5 | 6 | import javax.crypto.KeyGenerator; 7 | import javax.crypto.Mac; 8 | import javax.crypto.SecretKey; 9 | import javax.crypto.spec.SecretKeySpec; 10 | 11 | import org.apache.commons.codec.binary.Base64; 12 | 13 | public abstract class Coder { 14 | public static final String KEY_SHA = "SHA"; 15 | public static final String KEY_MD5 = "MD5"; 16 | 17 | /** 18 | * MAC算法可选以下多种算法 19 | * 20 | *
 21 | 	 *  
 22 | 	 * HmacMD5  
 23 | 	 * HmacSHA1  
 24 | 	 * HmacSHA256  
 25 | 	 * HmacSHA384  
 26 | 	 * HmacSHA512
 27 | 	 * 
28 | */ 29 | public static final String KEY_MAC = "HmacMD5"; 30 | 31 | /** 32 | * BASE64解密 33 | * 34 | * @param key 35 | * @return 36 | * @throws Exception 37 | */ 38 | public static byte[] decryptBASE64(String key) throws Exception { 39 | return Base64.decodeBase64(key); 40 | } 41 | 42 | /** 43 | * BASE64加密 44 | * 45 | * @param key 46 | * @return 47 | * @throws Exception 48 | */ 49 | public static String encryptBASE64(byte[] key) throws Exception { 50 | return Base64.encodeBase64String(key); 51 | } 52 | 53 | /** 54 | * MD5加密 55 | * 56 | * @param data 57 | * @return 58 | * @throws Exception 59 | */ 60 | public static byte[] encryptMD5(byte[] data) throws Exception { 61 | 62 | MessageDigest md5 = MessageDigest.getInstance(KEY_MD5); 63 | md5.update(data); 64 | 65 | return md5.digest(); 66 | 67 | } 68 | 69 | /** 70 | * SHA加密 71 | * 72 | * @param data 73 | * @return 74 | * @throws Exception 75 | */ 76 | public static byte[] encryptSHA(byte[] data) throws Exception { 77 | 78 | MessageDigest sha = MessageDigest.getInstance(KEY_SHA); 79 | sha.update(data); 80 | 81 | return sha.digest(); 82 | 83 | } 84 | 85 | /** 86 | * 初始化HMAC密钥 87 | * 88 | * @return 89 | * @throws Exception 90 | */ 91 | public static String initMacKey() throws Exception { 92 | KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC); 93 | 94 | SecretKey secretKey = keyGenerator.generateKey(); 95 | return encryptBASE64(secretKey.getEncoded()); 96 | } 97 | 98 | /** 99 | * HMAC加密 100 | * 101 | * @param data 102 | * @param key 103 | * @return 104 | * @throws Exception 105 | */ 106 | public static byte[] encryptHMAC(byte[] data, String key) throws Exception { 107 | 108 | SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC); 109 | Mac mac = Mac.getInstance(secretKey.getAlgorithm()); 110 | mac.init(secretKey); 111 | 112 | return mac.doFinal(data); 113 | 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/util/Constants.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.util; 2 | 3 | public class Constants { 4 | public final static String SERVER_MODE = "server"; 5 | public final static String CLIENT_MODE = "client"; 6 | public final static String TEMP_PREFIX = "diff"; 7 | public final static int OBJECT_SIZE_LIMIT = 1024*1024*20; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/util/FileSyncUtil.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.util; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.text.DecimalFormat; 9 | import java.time.LocalDate; 10 | import java.time.LocalTime; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import com.laz.filesync.rysnc.checksums.FileChecksums; 16 | import com.laz.filesync.rysnc.util.RsyncException; 17 | 18 | public class FileSyncUtil { 19 | public static int STAERT_FLAG = 0x12345; 20 | public static String NEW_FILE_FLAG = "_new_rsync_file" + STAERT_FLAG; 21 | 22 | public static void getFileCheck(File root, File f, Map map,boolean blockCheck) { 23 | if (f.isDirectory()) { 24 | for (File file : f.listFiles()) { 25 | getFileCheck(root, file, map,blockCheck); 26 | } 27 | } else { 28 | FileChecksums checksums = new FileChecksums(f,blockCheck); 29 | String rootPath = root.getAbsolutePath(); 30 | String path = FileUtil.getRelativePath(f, rootPath); 31 | map.put(FileUtil.convertPath(path), checksums); 32 | } 33 | } 34 | public static void getFileCheckSums(File root, File f, Map map) { 35 | getFileCheck(root, f, map, false); 36 | } 37 | public static void getFileCheckSumsAndBlockSums(File root, File f, Map map) { 38 | getFileCheck(root, f, map, true); 39 | } 40 | public static synchronized List getServerTempFolder() { 41 | String tempPath = System.getProperty("java.io.tmpdir"); 42 | File tempFolder = new File(tempPath); 43 | List list = new ArrayList(); 44 | for (File f:tempFolder.listFiles()) { 45 | if (f.getName().startsWith(Constants.TEMP_PREFIX)) { 46 | LocalDate d = LocalDate.now(); 47 | int year = d.getYear(); 48 | if (f.getName().contains(year+"") || f.getName().contains((year+1)+"") || f.getName().contains((year-1)+"")) { 49 | list.add(f); 50 | } 51 | } 52 | } 53 | return list; 54 | } 55 | public static synchronized File getServerTempFile(String initName) { 56 | String tempPath = System.getProperty("java.io.tmpdir"); 57 | File tempFolder = new File(tempPath); 58 | if (!tempFolder.exists()) { 59 | tempFolder.mkdirs(); 60 | } 61 | // 生成本次需同步的差异文件存放目录 62 | String serverTempName = initName.replace(".zip", "_server.zip"); 63 | File diffZip = new File(tempFolder + File.separator + serverTempName); 64 | return diffZip; 65 | } 66 | 67 | public static byte[] generateFileDigest(File file) { 68 | FileInputStream fis = null; 69 | try { 70 | MessageDigest sha = MessageDigest.getInstance(com.laz.filesync.rysnc.util.Constants.MD5); 71 | fis = new FileInputStream(file); 72 | byte[] buf = new byte[com.laz.filesync.rysnc.util.Constants.BLOCK_SIZE]; 73 | int read = 0; 74 | while ((read = fis.read(buf)) > 0) { 75 | sha.update(buf, 0, read); 76 | } 77 | return sha.digest(); 78 | } catch (IOException e) { 79 | throw new RsyncException(e); 80 | } catch (NoSuchAlgorithmException e) { 81 | throw new RsyncException(e); 82 | } finally { 83 | if (fis != null) { 84 | try { 85 | fis.close(); 86 | } catch (IOException e) { 87 | throw new RsyncException(e); 88 | } 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * 获取时间字符串 95 | * 96 | * @return 97 | */ 98 | public static String getTimeStr() { 99 | LocalDate d = LocalDate.now(); 100 | int year = d.getYear(); 101 | int month = d.getMonthValue(); 102 | int date = d.getDayOfMonth(); 103 | 104 | LocalTime time = LocalTime.now(); 105 | int hour = time.getHour(); 106 | int minute = time.getMinute(); 107 | int secord = time.getSecond(); 108 | return year + "-" + month + "-" + date + "-" + hour + "-" + minute + "-" + secord; 109 | } 110 | 111 | public static void main(String[] args) { 112 | System.out.println(getTimeStr()); 113 | } 114 | 115 | private static ThreadLocal threadLocal = new ThreadLocal() { 116 | protected DecimalFormat initialValue() { 117 | DecimalFormat df = new DecimalFormat("######0.00"); 118 | return df; 119 | } 120 | }; 121 | 122 | public static String getDoubleValue(Double d) { 123 | DecimalFormat df = threadLocal.get(); 124 | return df.format(d); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | public class FileUtil { 10 | public static boolean createFile(File file) throws IOException { 11 | if (file.exists()) { 12 | return true; 13 | } 14 | File folder = file.getParentFile(); 15 | if (!folder.exists()) { 16 | folder.mkdirs(); 17 | } 18 | return file.createNewFile(); 19 | } 20 | public static Map counts= new HashMap(); 21 | public static synchronized void countDown(String key) { 22 | CountDownLatch latch = counts.get(key); 23 | if (latch!=null) { 24 | latch.countDown(); 25 | } else { 26 | CountDownLatch l = new CountDownLatch(1); 27 | counts.put(key, l); 28 | } 29 | } 30 | public static synchronized CountDownLatch newCount(String key) { 31 | if (counts.get(key)==null) { 32 | CountDownLatch latch = new CountDownLatch(1); 33 | counts.put(key, latch); 34 | return latch; 35 | } else { 36 | //说明先完成的文件上传,不需要等待 37 | return null; 38 | } 39 | } 40 | /** 41 | * linux系统获取的路径是/ window获取的路径是\,为了统一,统一转换成/ 42 | * @param path 43 | * @return 44 | */ 45 | public static String convertPath(String path) { 46 | if (path !=null) { 47 | path = path.replace("\\", "/"); 48 | } 49 | return path; 50 | } 51 | 52 | /** 53 | * 获取相对路径 54 | * @param f 文件 55 | * @param relative 相对路径 56 | * @return 文件绝对路径减去relative后的路径 57 | */ 58 | public static String getRelativePath(File f, String relative) { 59 | String fPath = f.getAbsolutePath(); 60 | String rp = new File(relative).getAbsolutePath(); 61 | int index = fPath.indexOf(rp) + rp.length(); 62 | String relativepath = index>fPath.length()-1?"":fPath.substring(index+1); 63 | return relativepath; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/util/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.TypeReference; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.handler.codec.http.FullHttpRequest; 8 | import io.netty.handler.codec.json.JsonObjectDecoder; 9 | import io.netty.util.CharsetUtil; 10 | 11 | public class JsonUtil { 12 | private JsonUtil() { 13 | 14 | } 15 | 16 | public static String toJson(Object o) { 17 | return JSON.toJSONString(o); 18 | } 19 | 20 | public static T fromJson(FullHttpRequest request, Class c) { 21 | ByteBuf jsonBuf = request.content(); 22 | String jsonStr = jsonBuf.toString(CharsetUtil.UTF_8); 23 | return JSON.parseObject(jsonStr, new TypeReference() {}); 24 | } 25 | 26 | public static T fromJson(String json, Class c) { 27 | return JSON.parseObject(json, new TypeReference() {}); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/util/PathMap.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.util; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * 路径map,统一路径分隔符识别为/ 7 | * 8 | * @author laz 9 | * 10 | */ 11 | public class PathMap extends HashMap { 12 | @Override 13 | public V get(Object key) { 14 | if (key instanceof String) { 15 | String path = (String)key; 16 | key = (K) FileUtil.convertPath(path); 17 | } 18 | return super.get(key); 19 | } 20 | 21 | @Override 22 | public V put(K key, V value) { 23 | if (key instanceof String) { 24 | String path = (String)key; 25 | key = (K) FileUtil.convertPath(path); 26 | } 27 | return super.put(key, value); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/laz/filesync/util/ZipUtils.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.util; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.util.ArrayList; 10 | import java.util.Enumeration; 11 | import java.util.List; 12 | import java.util.zip.ZipEntry; 13 | import java.util.zip.ZipFile; 14 | import java.util.zip.ZipOutputStream; 15 | 16 | public class ZipUtils { 17 | private static final int BUFFER_SIZE = 2 * 1024; 18 | 19 | /** 20 | * 21 | * 压缩成ZIP 方法1 22 | * 23 | * @param srcDir 压缩文件夹路径 24 | * 25 | * @param out 压缩文件输出流 26 | * 27 | * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构; 28 | * 29 | * false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败) 30 | * 31 | * @throws RuntimeException 压缩失败会抛出运行时异常 32 | * 33 | */ 34 | 35 | public static void toZip(String srcDir, OutputStream out, boolean KeepDirStructure) 36 | 37 | throws RuntimeException { 38 | 39 | long start = System.currentTimeMillis(); 40 | 41 | ZipOutputStream zos = null; 42 | 43 | try { 44 | 45 | zos = new ZipOutputStream(out); 46 | 47 | File sourceFile = new File(srcDir); 48 | 49 | compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure); 50 | 51 | long end = System.currentTimeMillis(); 52 | 53 | System.out.println("压缩完成,耗时:" + (end - start) + " ms"); 54 | 55 | } catch (Exception e) { 56 | 57 | throw new RuntimeException("zip error from ZipUtils", e); 58 | 59 | } finally { 60 | 61 | if (zos != null) { 62 | 63 | try { 64 | 65 | zos.close(); 66 | 67 | } catch (IOException e) { 68 | 69 | e.printStackTrace(); 70 | 71 | } 72 | 73 | } 74 | 75 | } 76 | 77 | } 78 | 79 | /** 80 | * 81 | * 压缩成ZIP 方法2 82 | * 83 | * @param srcFiles 需要压缩的文件列表 84 | * 85 | * @param out 压缩文件输出流 86 | * 87 | * @throws RuntimeException 压缩失败会抛出运行时异常 88 | * 89 | */ 90 | 91 | public static void toZip(List srcFiles, OutputStream out) throws RuntimeException { 92 | 93 | long start = System.currentTimeMillis(); 94 | 95 | ZipOutputStream zos = null; 96 | 97 | try { 98 | 99 | zos = new ZipOutputStream(out); 100 | 101 | for (File srcFile : srcFiles) { 102 | 103 | byte[] buf = new byte[BUFFER_SIZE]; 104 | 105 | zos.putNextEntry(new ZipEntry(srcFile.getName())); 106 | 107 | int len; 108 | 109 | FileInputStream in = new FileInputStream(srcFile); 110 | 111 | while ((len = in.read(buf)) != -1) { 112 | 113 | zos.write(buf, 0, len); 114 | 115 | } 116 | 117 | zos.closeEntry(); 118 | 119 | in.close(); 120 | 121 | } 122 | 123 | long end = System.currentTimeMillis(); 124 | 125 | System.out.println("压缩完成,耗时:" + (end - start) + " ms"); 126 | 127 | } catch (Exception e) { 128 | 129 | throw new RuntimeException("zip error from ZipUtils", e); 130 | 131 | } finally { 132 | 133 | if (zos != null) { 134 | 135 | try { 136 | 137 | zos.close(); 138 | 139 | } catch (IOException e) { 140 | 141 | e.printStackTrace(); 142 | 143 | } 144 | 145 | } 146 | 147 | } 148 | 149 | } 150 | 151 | /** 152 | * 153 | * 递归压缩方法 154 | * 155 | * @param sourceFile 源文件 156 | * 157 | * @param zos zip输出流 158 | * 159 | * @param name 压缩后的名称 160 | * 161 | * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构; 162 | * 163 | * false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败) 164 | * 165 | * @throws Exception 166 | * 167 | */ 168 | 169 | private static void compress(File sourceFile, ZipOutputStream zos, String name, 170 | 171 | boolean KeepDirStructure) throws Exception { 172 | 173 | byte[] buf = new byte[BUFFER_SIZE]; 174 | 175 | if (sourceFile.isFile()) { 176 | 177 | // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字 178 | 179 | zos.putNextEntry(new ZipEntry(name)); 180 | 181 | // copy文件到zip输出流中 182 | 183 | int len; 184 | 185 | FileInputStream in = new FileInputStream(sourceFile); 186 | 187 | while ((len = in.read(buf)) != -1) { 188 | 189 | zos.write(buf, 0, len); 190 | 191 | } 192 | 193 | // Complete the entry 194 | 195 | zos.closeEntry(); 196 | 197 | in.close(); 198 | 199 | } else { 200 | 201 | File[] listFiles = sourceFile.listFiles(); 202 | 203 | if (listFiles == null || listFiles.length == 0) { 204 | 205 | // 需要保留原来的文件结构时,需要对空文件夹进行处理 206 | 207 | if (KeepDirStructure) { 208 | 209 | // 空文件夹的处理 210 | 211 | zos.putNextEntry(new ZipEntry(name + "/")); 212 | 213 | // 没有文件,不需要文件的copy 214 | 215 | zos.closeEntry(); 216 | 217 | } 218 | 219 | } else { 220 | 221 | for (File file : listFiles) { 222 | 223 | // 判断是否需要保留原来的文件结构 224 | 225 | if (KeepDirStructure) { 226 | 227 | // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠, 228 | 229 | // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了 230 | 231 | compress(file, zos, name + "/" + file.getName(), KeepDirStructure); 232 | 233 | } else { 234 | 235 | compress(file, zos, file.getName(), KeepDirStructure); 236 | 237 | } 238 | 239 | } 240 | 241 | } 242 | 243 | } 244 | 245 | } 246 | 247 | /** 248 | * 249 | * zip解压 250 | * 251 | * @param srcFile zip源文件 252 | * 253 | * @param destDirPath 解压后的目标文件夹 254 | * 255 | * @throws RuntimeException 解压失败会抛出运行时异常 256 | * 257 | */ 258 | public static void unzipFile(File srcFile, String destDirPath) { 259 | // 判断源文件是否存在 260 | if (!srcFile.exists()) { 261 | throw new RuntimeException(srcFile.getPath() + "所指文件不存在"); 262 | } 263 | // 开始解压 264 | ZipFile zipFile = null; 265 | try { 266 | zipFile = new ZipFile(srcFile); 267 | Enumeration entries = zipFile.entries(); 268 | while (entries.hasMoreElements()) { 269 | ZipEntry entry = (ZipEntry) entries.nextElement(); 270 | // 如果是文件夹,就创建个文件夹 271 | if (entry.isDirectory()) { 272 | String dirPath = destDirPath + File.separator + entry.getName(); 273 | File dir = new File(dirPath); 274 | dir.mkdirs(); 275 | } else { 276 | // 如果是文件,就先创建一个文件,然后用io流把内容copy过去 277 | File targetFile = new File(destDirPath + File.separator + entry.getName()); 278 | // 保证这个文件的父文件夹必须要存在 279 | if (!targetFile.getParentFile().exists()) { 280 | targetFile.getParentFile().mkdirs(); 281 | } 282 | targetFile.createNewFile(); 283 | // 将压缩文件内容写入到这个文件中 284 | InputStream is = zipFile.getInputStream(entry); 285 | FileOutputStream fos = new FileOutputStream(targetFile); 286 | int len; 287 | byte[] buf = new byte[BUFFER_SIZE]; 288 | while ((len = is.read(buf)) != -1) { 289 | fos.write(buf, 0, len); 290 | } 291 | // 关流顺序,先打开的后关闭 292 | fos.close(); 293 | is.close(); 294 | } 295 | } 296 | 297 | } catch (Exception e) { 298 | 299 | throw new RuntimeException("unzip error from ZipUtils", e); 300 | 301 | } finally { 302 | 303 | if (zipFile != null) { 304 | 305 | try { 306 | 307 | zipFile.close(); 308 | 309 | } catch (IOException e) { 310 | 311 | e.printStackTrace(); 312 | 313 | } 314 | 315 | } 316 | 317 | } 318 | } 319 | 320 | private static void BufferedInputStream(FileInputStream fileInputStream) { 321 | // TODO Auto-generated method stub 322 | 323 | } 324 | 325 | public static void main(String[] args) throws Exception { 326 | 327 | /** 测试压缩方法1 */ 328 | 329 | FileOutputStream fos1 = new FileOutputStream(new File("c:/mytest01.zip")); 330 | 331 | ZipUtils.toZip("D:/log", fos1, true); 332 | 333 | /** 测试压缩方法2 */ 334 | 335 | List fileList = new ArrayList<>(); 336 | 337 | fileList.add(new File("D:/Java/jdk1.7.0_45_64bit/bin/jar.exe")); 338 | 339 | fileList.add(new File("D:/Java/jdk1.7.0_45_64bit/bin/java.exe")); 340 | 341 | FileOutputStream fos2 = new FileOutputStream(new File("c:/mytest02.zip")); 342 | 343 | ZipUtils.toZip(fileList, fos2); 344 | 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger = info,stdout,D,E 2 | 3 | ### 输出信息到控制抬 ### 4 | log4j.appender.stdout = org.apache.log4j.ConsoleAppender 5 | log4j.appender.stdout.Target = System.out 6 | log4j.appender.stdout.layout = org.apache.log4j.PatternLayout 7 | log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l %m%n 8 | 9 | ### 输出DEBUG 级别以上的日志到=logs/log.log ### 10 | ### 按日期org.apache.log4j.DailyRollingFileAppender 11 | ### 按文件大小org.apache.log4j.DailyRollingFileAppender 12 | log4j.appender.D =org.apache.log4j.DailyRollingFileAppender 13 | log4j.appender.D.File = logs/log.log 14 | ### log4j.appender.D.MaxFileSize=1KB 15 | log4j.appender.D.Append = true 16 | log4j.appender.D.Threshold = DEBUG 17 | log4j.appender.D.layout = org.apache.log4j.PatternLayout 18 | log4j.appender.D.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n 19 | 20 | ### 输出ERROR 级别以上的日志到=logs/error.log ### 21 | log4j.appender.E = org.apache.log4j.DailyRollingFileAppender 22 | log4j.appender.E.File =logs/error.log 23 | ### log4j.appender.E.MaxFileSize=1KB 24 | log4j.appender.E.Append = true 25 | log4j.appender.E.Threshold = ERROR 26 | log4j.appender.E.layout = org.apache.log4j.PatternLayout 27 | log4j.appender.E.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n -------------------------------------------------------------------------------- /src/main/resources/small_client.txt: -------------------------------------------------------------------------------- 1 | 0123++++.0456789ab0cdef++0123*cdef -------------------------------------------------------------------------------- /src/main/resources/small_server.txt: -------------------------------------------------------------------------------- 1 | 0123++++456789abcdef -------------------------------------------------------------------------------- /src/test/java/com/laz/filesync/test/TestSync.java: -------------------------------------------------------------------------------- 1 | package com.laz.filesync.test; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.junit.Test; 8 | 9 | import com.laz.filesync.rysnc.checksums.DiffCheckItem; 10 | import com.laz.filesync.rysnc.checksums.FileChecksums; 11 | import com.laz.filesync.rysnc.checksums.RollingChecksum; 12 | import com.laz.filesync.rysnc.util.Constants; 13 | import com.laz.filesync.rysnc.util.QuickMD5; 14 | import com.laz.filesync.rysnc.util.RsyncFileUtils; 15 | 16 | public class TestSync { 17 | @Test 18 | public void testCheckSum() throws Exception { 19 | File srcFile = new File("C:\\Users\\lz578\\Desktop\\worldwind.jar"); 20 | File updateFile = new File("D:\\server\\apache-tomcat-8.0.36-2\\webapps\\SMS\\com\\sunsheen\\jfids\\demo\\worldwind\\worldwind.jar"); 21 | System.out.println(QuickMD5.getFileMD5Buffer(srcFile)); 22 | System.out.println(RsyncFileUtils.checkFileSame(updateFile, srcFile)); 23 | } 24 | @Test 25 | public void testRolling() throws Exception { 26 | 27 | File srcFile = new File("C:\\Users\\lz578\\Desktop\\worldwind.jar"); 28 | File updateFile = new File("D:\\server\\apache-tomcat-8.0.36-2\\webapps\\SMS\\com\\sunsheen\\jfids\\demo\\worldwind\\worldwind.jar"); 29 | File tmp = new File("D:\\filesync\\server\\1.txt_tmp"); 30 | File newFile = new File("D:\\filesync\\server\\1.txt_new"); 31 | long t1 = System.currentTimeMillis(); 32 | if (!tmp.exists()) { 33 | tmp.createNewFile(); 34 | } 35 | List dciList = roll(srcFile, updateFile); 36 | 37 | long t2 = System.currentTimeMillis(); 38 | 39 | System.out.println("滚动计算: spend time :" + (long) (t2 - t1) + "ms"); 40 | RsyncFileUtils.createRsyncFile(dciList, tmp, Constants.BLOCK_SIZE); 41 | 42 | System.out.println("实际需要传输的大小 :" + tmp.length() + " byte "); 43 | 44 | long t3 = System.currentTimeMillis(); 45 | 46 | System.out.println("生成临时文件,耗时 :" + (long) (t3 - t2) + "ms"); 47 | 48 | RsyncFileUtils.combineRsyncFile(srcFile, newFile, tmp); 49 | 50 | long t4 = System.currentTimeMillis(); 51 | 52 | System.out.println("合并文件 耗时:" + (long) (t4 - t3) + "ms"); 53 | 54 | System.out.println("all spend time :" + (long) (t4 - t1) + "ms"); 55 | 56 | System.out.println(RsyncFileUtils.checkFileSame(updateFile, newFile)); 57 | } 58 | 59 | private List roll(File srcFile, File updateFile) { 60 | 61 | FileChecksums fc = new FileChecksums(srcFile); 62 | 63 | List diffList = new ArrayList(); 64 | 65 | RollingChecksum rck = new RollingChecksum(fc, updateFile, diffList); 66 | 67 | rck.rolling(); 68 | 69 | return diffList; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/resources/diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/src/test/resources/diff -------------------------------------------------------------------------------- /src/test/resources/lorem: -------------------------------------------------------------------------------- 1 |  2 |  !"#$%&'()*+,-./012345Z dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 3 | 4 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 5 | 6 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 7 | 8 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 9 | 10 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 11 | 12 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 13 | 14 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 15 | 16 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 17 | 18 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 19 | 20 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 21 | 22 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 23 | 24 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 25 | 26 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 27 | 28 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. -------------------------------------------------------------------------------- /target/classes/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Built-By: laz 3 | Build-Jdk: 1.8.0_60 4 | Created-By: Maven Integration for Eclipse 5 | 6 | -------------------------------------------------------------------------------- /target/classes/META-INF/maven/com.laz/filesync/pom.properties: -------------------------------------------------------------------------------- 1 | #Generated by Maven Integration for Eclipse 2 | #Tue Jul 21 09:19:54 CST 2020 3 | version=0.0.1-SNAPSHOT 4 | groupId=com.laz 5 | m2e.projectName=filesync 6 | m2e.projectLocation=D\:\\develop\\ability\\filesync 7 | artifactId=filesync 8 | -------------------------------------------------------------------------------- /target/classes/META-INF/maven/com.laz/filesync/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | com.laz 6 | filesync 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | UTF-8 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | 18 | commons-codec 19 | commons-codec 20 | 1.13 21 | 22 | 23 | 24 | io.netty 25 | netty-all 26 | 4.1.25.Final 27 | 28 | 29 | 30 | commons-cli 31 | commons-cli 32 | 1.4 33 | 34 | 35 | org.slf4j 36 | slf4j-api 37 | 1.7.25 38 | 39 | 40 | 41 | 42 | org.slf4j 43 | slf4j-log4j12 44 | 1.7.25 45 | 46 | 47 | 48 | 49 | com.alibaba 50 | fastjson 51 | 1.2.58 52 | 53 | 54 | org.apache.commons 55 | commons-io 56 | 1.3.2 57 | 58 | 59 | 60 | 61 | src/main/java 62 | 63 | 64 | src/main/resources 65 | 66 | **/*.java 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-surefire-plugin 75 | 76 | true 77 | 78 | 79 | 80 | maven-compiler-plugin 81 | 3.1 82 | 83 | 1.8 84 | 1.8 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-shade-plugin 90 | 91 | 92 | package 93 | 94 | shade 95 | 96 | 97 | 98 | 99 | *:* 100 | 101 | META-INF/*.SF 102 | META-INF/*.DSA 103 | META-INF/*.RSA 104 | 105 | 106 | 107 | 108 | 109 | com.laz.filesync.FileSyncMain 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/FileSyncMain.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/FileSyncMain.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/client/FileSendClient$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/client/FileSendClient$1.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/client/FileSendClient.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/client/FileSendClient.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/client/FileSyncClient$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/client/FileSyncClient$1.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/client/FileSyncClient.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/client/FileSyncClient.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/client/file/handler/MessageEncoder.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/client/file/handler/MessageEncoder.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/client/handler/MsgClientHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/client/handler/MsgClientHandler.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/client/msg/DiffFilesSyncMsg.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/client/msg/DiffFilesSyncMsg.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/client/msg/RequestMsg.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/client/msg/RequestMsg.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/conf/Configuration.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/conf/Configuration.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/msg/BaseMsg.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/msg/BaseMsg.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/msg/ErrorMsg$Code.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/msg/ErrorMsg$Code.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/msg/ErrorMsg.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/msg/ErrorMsg.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/msg/MsgType.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/msg/MsgType.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/checksums/BlockChecksums.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/checksums/BlockChecksums.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/checksums/DiffCheckItem.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/checksums/DiffCheckItem.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/checksums/DiffFileMeta.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/checksums/DiffFileMeta.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/checksums/FileChecksums.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/checksums/FileChecksums.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/checksums/RollingChecksum.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/checksums/RollingChecksum.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/util/ByteTool.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/util/ByteTool.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/util/Constants.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/util/Constants.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/util/QuickMD5.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/util/QuickMD5.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/util/RsyncException.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/util/RsyncException.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/rysnc/util/RsyncFileUtils.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/rysnc/util/RsyncFileUtils.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/FileReceiveServer$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/FileReceiveServer$1.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/FileReceiveServer.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/FileReceiveServer.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/FileSyncServer$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/FileSyncServer$1.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/FileSyncServer.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/FileSyncServer.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/file/handler/FileReceiveServerHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/file/handler/FileReceiveServerHandler.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/file/handler/FileSendClientHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/file/handler/FileSendClientHandler.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/handler/MsgServerHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/handler/MsgServerHandler.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/msg/FileCheckSumsMsg.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/msg/FileCheckSumsMsg.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/server/msg/FileInfo.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/server/msg/FileInfo.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/util/Coder.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/util/Coder.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/util/Constants.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/util/Constants.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/util/FileSyncUtil$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/util/FileSyncUtil$1.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/util/FileSyncUtil.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/util/FileSyncUtil.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/util/JsonUtil$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/util/JsonUtil$1.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/util/JsonUtil$2.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/util/JsonUtil$2.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/util/JsonUtil.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/util/JsonUtil.class -------------------------------------------------------------------------------- /target/classes/com/laz/filesync/util/ZipUtils.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/classes/com/laz/filesync/util/ZipUtils.class -------------------------------------------------------------------------------- /target/classes/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger = info,stdout,D,E 2 | 3 | ### 输出信息到控制抬 ### 4 | log4j.appender.stdout = org.apache.log4j.ConsoleAppender 5 | log4j.appender.stdout.Target = System.out 6 | log4j.appender.stdout.layout = org.apache.log4j.PatternLayout 7 | log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l %m%n 8 | 9 | ### 输出DEBUG 级别以上的日志到=logs/log.log ### 10 | ### 按日期org.apache.log4j.DailyRollingFileAppender 11 | ### 按文件大小org.apache.log4j.DailyRollingFileAppender 12 | log4j.appender.D =org.apache.log4j.DailyRollingFileAppender 13 | log4j.appender.D.File = logs/log.log 14 | ### log4j.appender.D.MaxFileSize=1KB 15 | log4j.appender.D.Append = true 16 | log4j.appender.D.Threshold = DEBUG 17 | log4j.appender.D.layout = org.apache.log4j.PatternLayout 18 | log4j.appender.D.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n 19 | 20 | ### 输出ERROR 级别以上的日志到=logs/error.log ### 21 | log4j.appender.E = org.apache.log4j.DailyRollingFileAppender 22 | log4j.appender.E.File =logs/error.log 23 | ### log4j.appender.E.MaxFileSize=1KB 24 | log4j.appender.E.Append = true 25 | log4j.appender.E.Threshold = ERROR 26 | log4j.appender.E.layout = org.apache.log4j.PatternLayout 27 | log4j.appender.E.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n -------------------------------------------------------------------------------- /target/classes/small_client.txt: -------------------------------------------------------------------------------- 1 | 0123++++.0456789ab0cdef++0123*cdef -------------------------------------------------------------------------------- /target/classes/small_server.txt: -------------------------------------------------------------------------------- 1 | 0123++++456789abcdef -------------------------------------------------------------------------------- /target/test-classes/com/laz/filesync/test/TestSync.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/test-classes/com/laz/filesync/test/TestSync.class -------------------------------------------------------------------------------- /target/test-classes/diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazlaz/filesync/06d860bb43ddde04041858ce4132caa715d31de3/target/test-classes/diff -------------------------------------------------------------------------------- /target/test-classes/lorem: -------------------------------------------------------------------------------- 1 |  2 |  !"#$%&'()*+,-./012345Z dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 3 | 4 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 5 | 6 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 7 | 8 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 9 | 10 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 11 | 12 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 13 | 14 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 15 | 16 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 17 | 18 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 19 | 20 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 21 | 22 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 23 | 24 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 25 | 26 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 27 | 28 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. --------------------------------------------------------------------------------