├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml ├── runClient.bat ├── runServer.bat ├── runServer.sh └── src ├── main └── java │ └── com │ └── yhs │ └── fileserver │ ├── client │ ├── Client.java │ └── ClientHandler.java │ ├── common │ ├── ACTION.java │ ├── Constant.java │ ├── FileUtil.java │ └── MarshallingCodeCFactory.java │ ├── core │ ├── CompareServer.java │ └── FileScan.java │ ├── pojo │ ├── Request.java │ └── Response.java │ └── server │ ├── Server.java │ └── ServerHandler.java └── test └── java └── com └── yhs └── autoupdate └── autoupdate └── AppTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # maven ignore 2 | target/ 3 | *.war 4 | *.zip 5 | *.tar 6 | *.tar.gz 7 | 8 | # eclipse ignore 9 | .settings/ 10 | .project 11 | .classpath 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | logs/ 30 | bin/ 31 | *.class 32 | 33 | # Mobile Tools for Java (J2ME) 34 | .mtj.tmp/ 35 | 36 | # Package Files # 37 | *.jar 38 | *.war 39 | *.ear 40 | 41 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 42 | hs_err_pid* 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fileserver 2 | 一个基于netty的文件服务器,可以用于分发应用程序,文件md5校验比对差量更新,支持大文件传输 3 | 4 | ## 使用 5 | ```shell 6 | git clone https://github.com/huisongyang/fileserver.git 7 | mvn package 8 | ``` 9 | ### windows: 10 | 编辑./target/runServer.bat,配置root.dir.path 11 | ```bat 12 | @echo off 13 | rem 文件根目录 14 | set root.dir.path="D:\serverdir" 15 | 16 | ::不扫描文件,分号分隔 17 | set root.dir.exclude= 18 | ::不扫描目录,分号分隔 19 | set root.dir.excludeDir= 20 | 21 | ::默认端口是9999 22 | java -Droot.dir.path=%root.dir.path% -Droot.dir.exclude=%root.dir.exclude% -Droot.dir.excludeDir=%root.dir.excludeDir% -cp fileserver-0.0.1-SNAPSHOT.jar com.yhs.fileserver.server.Server 23 | pause > nul 24 | ``` 25 | 运行runServer.bat 26 | 27 | 编辑./target/runClient.bat,配置root.dir.path 28 | 运行runClient.bat 29 | 30 | ### linux: 31 | 编辑./target/runServer.sh,配置root.dir.path 32 | ```shell 33 | #根目录 34 | root_dir_path=/home/weblogic/client 35 | 36 | #不扫描文件,分号分隔 37 | root_dir_exclude= 38 | #不扫描目录,分号分隔 39 | root_dir_excludeDir= 40 | 41 | #默认端口是9999 42 | java -Droot.dir.path=${root_dir_path} -Droot.dir.exclude=${root_dir_exclude} -Droot.dir.excludeDir=${root_dir_excludeDir} -cp fileserver-0.0.1-SNAPSHOT.jar com.yhs.fileserver.server.Server 43 | ``` 44 | 客户端更新一般在windows下,在windows下配置好runClient.bat的root.dir.path后运行即可。 45 | 46 | #Authors 47 | *小松([@小松](https://github.com/huisongyang)) 48 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.yhs.fileserver 6 | fileserver 7 | 0.0.1-SNAPSHOT 8 | 9 | fileserver 10 | http://maven.apache.org 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | 18 | junit 19 | junit 20 | 3.8.1 21 | test 22 | 23 | 24 | io.netty 25 | netty-all 26 | 5.0.0.Alpha1 27 | 28 | 29 | org.jboss.marshalling 30 | jboss-marshalling 31 | 1.4.10.Final 32 | 33 | 34 | org.jboss.marshalling 35 | jboss-marshalling-serial 36 | 1.4.10.Final 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-shade-plugin 45 | 1.4 46 | 47 | 48 | package 49 | 50 | shade 51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | maven-antrun-plugin 65 | 1.8 66 | 67 | 68 | package 69 | 70 | 71 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | run 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /runClient.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem 配置文件根目录,如果为空,则当前目录为根目录 4 | set root.dir.path="D:\workspace\test" 5 | 6 | if "%root.dir.path%"=="" set root.dir.path="%CD%" 7 | 8 | rem 不删除文件列表,分号分隔 9 | set root.dir.exclude="fileserver-0.0.1-SNAPSHOT.jar;clientMd5.record;runClient.bat;Client.log" 10 | rem 不删除目录列表,分号分隔 11 | set root.dir.excludeDir="log" 12 | 13 | rem 启动更新 14 | java -Droot.dir.path=%root.dir.path% -Droot.dir.exclude=%root.dir.exclude% -Droot.dir.excludeDir=%root.dir.excludeDir% -cp fileserver-0.0.1-SNAPSHOT.jar com.yhs.fileserver.client.Client 127.0.0.1 9999 15 | if "%errorlevel%"=="0" echo [info]: 连接关闭 16 | 17 | rem 启动应用 18 | echo done 19 | -------------------------------------------------------------------------------- /runServer.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem 文件根目录 3 | set root.dir.path="D:\serverdir" 4 | 5 | ::不扫描文件,分号分隔 6 | set root.dir.exclude= 7 | ::不扫描目录,分号分隔 8 | set root.dir.excludeDir= 9 | 10 | ::默认端口是9999 11 | java -Droot.dir.path=%root.dir.path% -Droot.dir.exclude=%root.dir.exclude% -Droot.dir.excludeDir=%root.dir.excludeDir% -cp fileserver-0.0.1-SNAPSHOT.jar com.yhs.fileserver.server.Server 12 | -------------------------------------------------------------------------------- /runServer.sh: -------------------------------------------------------------------------------- 1 | #根目录 2 | root_dir_path=/home/weblogic/client 3 | 4 | #不扫描文件,分号分隔 5 | root_dir_exclude= 6 | #不扫描目录,分号分隔 7 | root_dir_excludeDir= 8 | 9 | #默认端口是9999 10 | java -Droot.dir.path=${root_dir_path} -Droot.dir.exclude=${root_dir_exclude} -Droot.dir.excludeDir=${root_dir_excludeDir} -cp fileserver-0.0.1-SNAPSHOT.jar com.yhs.fileserver.server.Server 11 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/client/Client.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.client; 2 | 3 | import java.net.InetAddress; 4 | 5 | import com.yhs.fileserver.common.MarshallingCodeCFactory; 6 | import com.yhs.fileserver.core.FileScan; 7 | 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.channel.ChannelFuture; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelOption; 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.NioSocketChannel; 16 | 17 | /** 18 | * 19 | * @author huisong 20 | * 21 | */ 22 | public class Client { 23 | 24 | public static String ip ; 25 | 26 | public void connect(int port, String host) throws Exception { 27 | // 配置客户端NIO线程组 28 | EventLoopGroup group = new NioEventLoopGroup(); 29 | try { 30 | Bootstrap b = new Bootstrap(); 31 | b.group(group).channel(NioSocketChannel.class) 32 | .option(ChannelOption.TCP_NODELAY, true) 33 | .handler(new ChannelInitializer() { 34 | @Override 35 | public void initChannel(SocketChannel ch) 36 | throws Exception { 37 | ch.pipeline().addLast("marDecoder",MarshallingCodeCFactory.buildMarshallingDecoder()); 38 | ch.pipeline().addLast("marEncoder", 39 | MarshallingCodeCFactory 40 | .buildMarshallingEncoder()); 41 | ch.pipeline().addLast(new ClientHandler()); 42 | } 43 | }); 44 | // 发起异步连接操作 45 | ChannelFuture f = b.connect(host, port).sync(); 46 | 47 | // 等待客户端链路关闭 48 | f.channel().closeFuture().sync(); 49 | } finally { 50 | // 优雅退出,释放NIO线程组 51 | group.shutdownGracefully(); 52 | } 53 | } 54 | 55 | /** 56 | * @param args 57 | * @throws Exception 58 | */ 59 | public static void main(String[] args) throws Exception { 60 | int port = 9999; 61 | String host = "127.0.0.1"; 62 | if (args != null && args.length > 0) { 63 | try { 64 | host = args[0]; 65 | port = Integer.valueOf(args[1]); 66 | } catch (NumberFormatException e) { 67 | // 采用默认值 68 | } 69 | } 70 | FileScan.getDefault().scanningAndWrite("clientMd5.record"); 71 | ip = InetAddress.getLocalHost().toString(); 72 | new Client().connect(port, host); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/client/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.client; 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.OutputStream; 8 | import java.util.List; 9 | import java.util.Stack; 10 | 11 | import com.yhs.fileserver.common.ACTION; 12 | import com.yhs.fileserver.common.Constant; 13 | import com.yhs.fileserver.common.FileUtil; 14 | import com.yhs.fileserver.common.MarshallingCodeCFactory; 15 | import com.yhs.fileserver.core.FileScan; 16 | import com.yhs.fileserver.pojo.Request; 17 | import com.yhs.fileserver.pojo.Response; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerAdapter; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.util.CharsetUtil; 23 | 24 | /** 25 | * 26 | * @author huisong 27 | * 28 | */ 29 | public class ClientHandler extends ChannelHandlerAdapter { 30 | /** 31 | * Creates a client-side handler. 32 | */ 33 | 34 | File file; 35 | OutputStream out; 36 | long fileSize; 37 | long writedSize = 0; 38 | Stack downList; 39 | List deleteFiles; 40 | List newFiles; 41 | List overrideFiles; 42 | 43 | public ClientHandler() { 44 | 45 | } 46 | 47 | @Override 48 | public void channelActive(ChannelHandlerContext ctx) { 49 | ctx.write(startReq()); 50 | ctx.flush(); 51 | } 52 | 53 | /** 54 | * 更新请求 55 | * 56 | * @return 57 | */ 58 | private Request startReq() { 59 | byte[] bytes = null; 60 | try { 61 | bytes = FileUtil 62 | .readFile(com.yhs.fileserver.common.Constant.clientMd5); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | Request req = new Request(); 67 | req.setActionCode(ACTION.ACTION_START); 68 | req.setData(bytes); 69 | req.setIp(Client.ip); 70 | return req; 71 | } 72 | 73 | @Override 74 | public void channelRead(ChannelHandlerContext ctx, Object msg) 75 | throws Exception { 76 | // consoleLog(msg.toString()); 77 | if (msg instanceof ByteBuf) { 78 | ByteBuf buf = (ByteBuf) msg; 79 | if (out == null) 80 | throw new Exception("本地文件流没有初始化"); 81 | int len = buf.readableBytes(); 82 | 83 | // 如果文件大小是0KB,更新服务器则会直接响应个文件结尾符,文件结尾符长度是6 84 | if (len == 6) { 85 | byte[] endBuf = new byte[6]; 86 | buf.readBytes(endBuf, 0, 6); 87 | if (Constant.FILE_SEPARATOR.equals(new String(endBuf,CharsetUtil.UTF_8))) { 88 | consoleLog("空文件结尾符"); 89 | closeFileAndOut(ctx); 90 | buf.release(); 91 | fireDownReq(ctx); 92 | return; 93 | } 94 | else{ 95 | buf.readerIndex(0); 96 | } 97 | } 98 | 99 | buf.readBytes(out, len); 100 | writedSize += len; 101 | if (writedSize == fileSize) { 102 | closeFileAndOut(ctx); 103 | buf.release(); 104 | fireDownReq(ctx); 105 | } 106 | } else if (msg instanceof Response) { 107 | Response resp = (Response) msg; 108 | String resType = resp.getResType(); 109 | if (resType.equals(Constant.TYPE_GET_DIFFERENT))// 差异列表 110 | { 111 | responseRead(ctx, resp); 112 | fireDownReq(ctx); 113 | deleteFiles(); 114 | } else if (resType.equals(Constant.TYPE_GET_SIZE))// 文件大小 115 | { 116 | if (!resp.getResCode().equals(Constant.SuccessCode)) 117 | throw new Exception(resp.getResMessage()); 118 | fileSize = Long.parseLong(resp.getResMessage()); 119 | String fn = resp.getReq().getReqMes();// 文件名 120 | initFile(fn);// 初始化文件流,准备发起下载请求 121 | ctx.pipeline().remove("marDecoder"); 122 | beginDownFile(ctx, fn); 123 | } 124 | } 125 | } 126 | 127 | /** 128 | * 开始下载文件 129 | * 130 | * @param ctx 131 | * @param fn 132 | */ 133 | private void beginDownFile(ChannelHandlerContext ctx, String fn) { 134 | consoleLog("下载文件:" + fn); 135 | Request req = new Request(); 136 | req.setActionCode(ACTION.ACTION_DOWN); 137 | req.setReqMes(fn); 138 | req.setIp(Client.ip); 139 | ctx.writeAndFlush(req); 140 | } 141 | 142 | /** 143 | * 发起文件下载请求
144 | * 按顺序发起下载请求,每次下载完一个文件,则重新请求下一个文件的下载
145 | * 每次发起都要保证保存及关闭当前文件 146 | * 147 | * @see 148 | * @param ctx 149 | * @throws Exception 150 | */ 151 | private void fireDownReq(ChannelHandlerContext ctx) throws Exception { 152 | if (downList.empty()) { 153 | ctx.close(); 154 | consoleLog("更新完成"); 155 | return; 156 | } 157 | String fn = downList.pop(); 158 | getFileSize(ctx, fn);// 获取文件大小 159 | } 160 | 161 | private void getFileSize(ChannelHandlerContext ctx, String fn) 162 | throws Exception { 163 | if (fn == null) 164 | throw new Exception("文件名不能为空"); 165 | 166 | Request req = new Request(); 167 | req.setActionCode(ACTION.ACTION_GET_FILE_SIZE); 168 | req.setIp(Client.ip); 169 | req.setReqMes(fn);// 文件名 170 | ctx.writeAndFlush(req); 171 | } 172 | 173 | private void initFile(String fn) { 174 | try { 175 | file = new File(FileScan.rootDirPath + fn); 176 | if (file.exists()) { 177 | file.delete(); 178 | file.createNewFile(); 179 | } else { 180 | File parent = file.getParentFile(); 181 | if (!parent.exists()) { 182 | parent.mkdirs(); 183 | } 184 | } 185 | out = new FileOutputStream(file); 186 | } catch (FileNotFoundException e) { 187 | e.printStackTrace(); 188 | } catch (IOException e) { 189 | e.printStackTrace(); 190 | } 191 | } 192 | 193 | /** 194 | * 195 | * 响应文件差异列表
196 | * 197 | * @param ctx 198 | * @param msg 199 | */ 200 | @SuppressWarnings("unchecked") 201 | private void responseRead(ChannelHandlerContext ctx, Response resp) { 202 | 203 | if (resp.getResCode().equals( 204 | com.yhs.fileserver.common.Constant.SuccessCode)) { 205 | downList = new Stack(); 206 | consoleLog(resp.getResMessage()); 207 | deleteFiles = (List) resp.getDeleteFiles(); 208 | newFiles = (List) resp.getNewFiles(); 209 | overrideFiles = (List) resp.getOverrideFiles(); 210 | for (String s : newFiles) { 211 | downList.push(s); 212 | } 213 | for (String i : overrideFiles) { 214 | downList.push(i); 215 | } 216 | } 217 | } 218 | 219 | /** 220 | * 根据更新服务器返回的需删除文件列表进行文件的删除
221 | * 以及清空空文件夹 222 | */ 223 | private void deleteFiles() { 224 | if (!deleteFiles.isEmpty()) { 225 | Thread t = new Thread(new Runnable() { 226 | 227 | public void run() { 228 | for (String fn : deleteFiles) { 229 | File f = new File(FileScan.rootDirPath + fn); 230 | if (f.exists()) { 231 | f.delete(); 232 | consoleLog("删除文件:" + f.getName()); 233 | } 234 | } 235 | deleteEmptyDir(new File(FileScan.rootDirPath)); 236 | } 237 | }); 238 | 239 | t.start(); 240 | } 241 | } 242 | 243 | /** 244 | * 递归删除空文件夹 245 | * 246 | * @param f 247 | */ 248 | private void deleteEmptyDir(File f) { 249 | for (File i : f.listFiles()) { 250 | if (i.isDirectory()) { 251 | deleteEmptyDir(i); 252 | if (i.isDirectory() && i.list().length == 0) { 253 | i.delete(); 254 | consoleLog("清空空文件夹:" + i.getName()); 255 | } 256 | } 257 | } 258 | } 259 | 260 | /** 261 | * 关闭文件流
262 | * 每次从服务器获得一个完整文件后调用 263 | * 264 | * @author huisong 265 | */ 266 | private void closeFileAndOut(ChannelHandlerContext ctx) { 267 | try { 268 | fileSize = 0; 269 | writedSize = 0; 270 | out.close(); 271 | file = null; 272 | ctx.pipeline().addBefore("marEncoder", "marDecoder", 273 | MarshallingCodeCFactory.buildMarshallingDecoder()); 274 | } catch (IOException e) { 275 | e.printStackTrace(); 276 | } 277 | 278 | } 279 | 280 | private void consoleLog(String msg) { 281 | System.out.println("[info]: " + msg); 282 | } 283 | 284 | 285 | @Override 286 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 287 | ctx.flush(); 288 | } 289 | 290 | @Override 291 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 292 | cause.printStackTrace(); 293 | ctx.close(); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/common/ACTION.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.common; 2 | 3 | public class ACTION { 4 | 5 | /** 6 | *获取文件差异列表 7 | */ 8 | public static int ACTION_START = 0x001; 9 | 10 | /** 11 | * 下载文件 12 | */ 13 | public static int ACTION_DOWN = 0x002; 14 | 15 | /** 16 | * 获取文件大小 17 | */ 18 | public static int ACTION_GET_FILE_SIZE = 0x006; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/common/Constant.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.common; 2 | 3 | /** 4 | * 5 | * @author huisong 6 | * 7 | */ 8 | public class Constant { 9 | 10 | /** 11 | * 文件结尾符 12 | */ 13 | public static String FILE_SEPARATOR = "□□"; 14 | 15 | /** 16 | * 客户端md5校验文件 17 | */ 18 | public static String clientMd5 = "clientMd5.record"; 19 | 20 | /** 21 | * 服务器端md5校验文件 22 | */ 23 | public static String serverMd5 = "md5.record"; 24 | 25 | /** 26 | * 27 | */ 28 | public static String SuccessCode = "AAAAAA"; 29 | 30 | /** 31 | * 获取文件差异 32 | */ 33 | public static String TYPE_GET_DIFFERENT="TYPE_GET_DIFFERENT"; 34 | 35 | /** 36 | * 获取文件大小 37 | */ 38 | public static String TYPE_GET_SIZE="TYPE_GET_SIZE"; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/common/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.common; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | 12 | public class FileUtil { 13 | 14 | public static byte[] readFile(String fileName) throws Exception 15 | { 16 | File f = new File(fileName); 17 | if(!f.exists()) 18 | throw new Exception("文件不存在"); 19 | InputStream ins = new FileInputStream(f); 20 | BufferedInputStream in = new BufferedInputStream(ins); 21 | int lenght = (int)f.length(); 22 | byte[] buffer = new byte[lenght]; 23 | in.read(buffer,0, lenght); 24 | in.close(); 25 | ins.close(); 26 | f=null; 27 | return buffer; 28 | } 29 | 30 | public static void writeFile(String fileName,byte[] bytes) throws IOException 31 | { 32 | File f = new File(fileName); 33 | if(!f.exists()) 34 | { 35 | f.createNewFile(); 36 | } 37 | else 38 | { 39 | f.delete(); 40 | f.createNewFile(); 41 | } 42 | OutputStream os = new FileOutputStream(f); 43 | BufferedOutputStream out = new BufferedOutputStream(os); 44 | out.write(bytes); 45 | out.close(); 46 | os.close(); 47 | f=null; 48 | } 49 | 50 | public static void deleteFile(String name) 51 | { 52 | File f = new File(name); 53 | if(f.exists()) 54 | { 55 | f.delete(); 56 | f=null; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/common/MarshallingCodeCFactory.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.common; 2 | 3 | import org.jboss.marshalling.MarshallerFactory; 4 | import org.jboss.marshalling.Marshalling; 5 | import org.jboss.marshalling.MarshallingConfiguration; 6 | 7 | import io.netty.handler.codec.marshalling.DefaultMarshallerProvider; 8 | import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider; 9 | import io.netty.handler.codec.marshalling.MarshallerProvider; 10 | import io.netty.handler.codec.marshalling.MarshallingDecoder; 11 | import io.netty.handler.codec.marshalling.MarshallingEncoder; 12 | import io.netty.handler.codec.marshalling.UnmarshallerProvider; 13 | 14 | public class MarshallingCodeCFactory { 15 | 16 | /** 17 | * 创建Jboss Marshalling解码器MarshallingDecoder 18 | * 19 | * @return 20 | */ 21 | public static MarshallingDecoder buildMarshallingDecoder() { 22 | final MarshallerFactory marshallerFactory = Marshalling 23 | .getProvidedMarshallerFactory("serial"); 24 | final MarshallingConfiguration configuration = new MarshallingConfiguration(); 25 | configuration.setVersion(5); 26 | UnmarshallerProvider provider = new DefaultUnmarshallerProvider( 27 | marshallerFactory, configuration); 28 | MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024*1024*10); 29 | return decoder; 30 | } 31 | 32 | /** 33 | * 创建Jboss Marshalling编码器MarshallingEncoder 34 | * 35 | * @return 36 | */ 37 | public static MarshallingEncoder buildMarshallingEncoder() { 38 | final MarshallerFactory marshallerFactory = Marshalling 39 | .getProvidedMarshallerFactory("serial"); 40 | final MarshallingConfiguration configuration = new MarshallingConfiguration(); 41 | configuration.setVersion(5); 42 | MarshallerProvider provider = new DefaultMarshallerProvider( 43 | marshallerFactory, configuration); 44 | MarshallingEncoder encoder = new MarshallingEncoder(provider); 45 | return encoder; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/core/CompareServer.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.core; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | /** 13 | * 客户端需要删除的文件列表
14 | * 客户端需要更新覆盖的文件列表
15 | * 客户端需要新增加的文件列表 16 | * @author huisong 17 | * 18 | */ 19 | public class CompareServer { 20 | 21 | 22 | 23 | String clientMd5FileName; 24 | String serverMd5FileName; 25 | File clientMd5File; 26 | File serverMd5File; 27 | List deleteList; 28 | List overrideList; 29 | List newList; 30 | 31 | Map clientMd5Map; 32 | Map serverMd5Map; 33 | 34 | public boolean isNeedUpdate() { 35 | 36 | if(deleteList.isEmpty()&&overrideList.isEmpty()&&newList.isEmpty()) 37 | { 38 | return false; 39 | } 40 | return true; 41 | } 42 | 43 | public List getDeleteList() { 44 | return deleteList; 45 | } 46 | 47 | public void setDeleteList(List deleteList) { 48 | this.deleteList = deleteList; 49 | } 50 | 51 | public List getOverrideList() { 52 | return overrideList; 53 | } 54 | 55 | public void setOverrideList(List overrideList) { 56 | this.overrideList = overrideList; 57 | } 58 | 59 | public List getNewList() { 60 | return newList; 61 | } 62 | 63 | public void setNewList(List newList) { 64 | this.newList = newList; 65 | } 66 | 67 | 68 | 69 | public CompareServer(String clientMd5, String serverMd5) { 70 | this.clientMd5FileName = clientMd5; 71 | this.serverMd5FileName = serverMd5; 72 | 73 | deleteList = new ArrayList(); 74 | overrideList = new ArrayList(); 75 | newList = new ArrayList(); 76 | 77 | clientMd5Map = new HashMap(); 78 | serverMd5Map = new HashMap(); 79 | } 80 | 81 | private void loadMd5File() throws Exception { 82 | 83 | clientMd5Map.clear(); 84 | serverMd5Map.clear(); 85 | this.clientMd5File = new File(clientMd5FileName); 86 | this.serverMd5File = new File(serverMd5FileName); 87 | 88 | if(!clientMd5File.exists()||!serverMd5File.exists()) 89 | { 90 | throw new Exception("md5校验文件缺失"); 91 | } 92 | 93 | FileReader c = new FileReader(clientMd5File); 94 | FileReader s = new FileReader(serverMd5File); 95 | 96 | BufferedReader cr = new BufferedReader(c); 97 | BufferedReader sr = new BufferedReader(s); 98 | String line; 99 | while ((line = cr.readLine()) != null && !line.equals("")) { 100 | String[] items = line.split("\\|"); 101 | 102 | clientMd5Map.put(items[0], items[1]); 103 | } 104 | cr.close(); 105 | while ((line = sr.readLine()) != null && !line.equals("")) { 106 | String[] items = line.split("\\|"); 107 | serverMd5Map.put(items[0], items[1]); 108 | } 109 | sr.close(); 110 | } 111 | 112 | public void compare() throws Exception { 113 | loadMd5File(); 114 | setdeleteAndOverrideList(); 115 | setNewList(); 116 | } 117 | 118 | private void setdeleteAndOverrideList() { 119 | Set k = serverMd5Map.keySet(); 120 | for(String i : clientMd5Map.keySet()) 121 | { 122 | if(!k.contains(i)) 123 | { 124 | deleteList.add(i); 125 | } 126 | else 127 | { 128 | String md51 = serverMd5Map.get(i); 129 | String md52 = clientMd5Map.get(i); 130 | if(!md51.equals(md52)) 131 | { 132 | overrideList.add(i); 133 | } 134 | } 135 | } 136 | } 137 | 138 | private void setNewList() 139 | { 140 | for(String i : serverMd5Map.keySet()) 141 | { 142 | if(!clientMd5Map.containsKey(i)) 143 | { 144 | newList.add(i); 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/core/FileScan.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.core; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileWriter; 7 | import java.io.IOException; 8 | import java.security.DigestInputStream; 9 | import java.security.MessageDigest; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * 文件扫描生成md5校验文件 16 | * 17 | * @author huisong 18 | * 19 | */ 20 | public class FileScan { 21 | 22 | public static String rootDirPath; 23 | MessageDigest md; 24 | int bufferSize = 1024 * 20; 25 | List digestList; 26 | List excludeList; 27 | List excludeDirList; 28 | 29 | private static final FileScan instance = new FileScan(); 30 | 31 | public static FileScan getDefault() { 32 | return instance; 33 | } 34 | 35 | FileScan() { 36 | rootDirPath = System.getProperty("root.dir.path"); 37 | digestList = new ArrayList(); 38 | excludeList = new ArrayList(); 39 | excludeDirList= new ArrayList(); 40 | getExcludeFiles(); 41 | getExcludeDir(); 42 | try { 43 | md = MessageDigest.getInstance("MD5"); 44 | } catch (NoSuchAlgorithmException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | private void getExcludeFiles() { 50 | String exclude = System.getProperty("root.dir.exclude"); 51 | if (exclude != null && !"".equals(exclude)) { 52 | String[] files = exclude.split(";"); 53 | for (String f : files) { 54 | excludeList.add(f); 55 | } 56 | } 57 | } 58 | 59 | private void getExcludeDir() 60 | { 61 | String excludeDir = System.getProperty("root.dir.excludeDir"); 62 | if(excludeDir != null && !"".equals(excludeDir)) 63 | { 64 | String[] dirs = excludeDir.split(";"); 65 | for(String d : dirs) 66 | { 67 | excludeDirList.add(d); 68 | } 69 | } 70 | } 71 | 72 | static class FileStructure { 73 | String fileName; 74 | String md5; 75 | 76 | public FileStructure(String f, String m) { 77 | this.fileName = f; 78 | this.md5 = m; 79 | } 80 | 81 | public String getFileName() { 82 | return fileName; 83 | } 84 | 85 | public void setFileName(String fileName) { 86 | this.fileName = fileName; 87 | } 88 | 89 | public String getMd5() { 90 | return md5; 91 | } 92 | 93 | public void setMd5(String md5) { 94 | this.md5 = md5; 95 | } 96 | 97 | } 98 | 99 | public void scanningAndWrite(String fileName) throws Exception { 100 | digestListClear(); 101 | if (rootDirPath == null||"".equals(rootDirPath)) { 102 | throw new Exception("root.dir.path不存在"); 103 | } 104 | File rootDir = new File(rootDirPath); 105 | if(!rootDir.exists()) 106 | throw new Exception("root.dir.path目录不存在"); 107 | scanning(rootDir); 108 | writeFile(fileName); 109 | } 110 | 111 | private void digestListClear() { 112 | digestList.clear(); 113 | } 114 | 115 | private void writeFile(String fileName) { 116 | 117 | File f; 118 | FileWriter writer; 119 | BufferedWriter bw; 120 | try { 121 | 122 | f = new File(fileName); 123 | if (f.exists()) 124 | f.delete(); 125 | f.createNewFile(); 126 | 127 | writer = new FileWriter(f); 128 | bw = new BufferedWriter(writer); 129 | 130 | for (FileStructure item : digestList) { 131 | String line = item.fileName.replace("\\", "/") + "|" + item.md5; 132 | bw.append(line); 133 | bw.newLine(); 134 | } 135 | bw.close(); 136 | writer.close(); 137 | } catch (IOException e) { 138 | e.printStackTrace(); 139 | } finally { 140 | 141 | } 142 | } 143 | 144 | public void scanning(File file) { 145 | for (File f : file.listFiles()) { 146 | if (f.isFile()) { 147 | 148 | if (!excludeList.contains(f.getName())) { 149 | try { 150 | String md5 = getMd5(f); 151 | String fileName = f.getPath().replace(rootDirPath, ""); 152 | FileStructure fs = new FileStructure(fileName, md5); 153 | digestList.add(fs); 154 | } catch (IOException e) { 155 | e.printStackTrace(); 156 | } 157 | } 158 | else 159 | { 160 | consoleLog("跳过文件校验:"+f.getName()); 161 | } 162 | } else if (f.isDirectory()) { 163 | if(!excludeDirList.contains(f.getName())) 164 | { 165 | scanning(f); 166 | }else 167 | { 168 | consoleLog("跳过目录校验:"+f.getName()); 169 | } 170 | } 171 | } 172 | } 173 | 174 | public String getMd5(File f) throws IOException { 175 | 176 | FileInputStream in = new FileInputStream(f); 177 | DigestInputStream di = new DigestInputStream(in, md); 178 | 179 | byte[] buffer = new byte[bufferSize]; 180 | while (di.read(buffer) > 0) 181 | ; 182 | md = di.getMessageDigest(); 183 | 184 | di.close(); 185 | in.close(); 186 | 187 | byte[] digest = md.digest(); 188 | return byteArrayToHex(digest); 189 | } 190 | 191 | private static String byteArrayToHex(byte[] byteArray) { 192 | 193 | // 首先初始化一个字符数组,用来存放每个16进制字符 194 | char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 195 | 'A', 'B', 'C', 'D', 'E', 'F' }; 196 | // new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方)) 197 | char[] resultCharArray = new char[byteArray.length * 2]; 198 | // 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去 199 | int index = 0; 200 | for (byte b : byteArray) { 201 | resultCharArray[index++] = hexDigits[b >>> 4 & 0xf]; 202 | resultCharArray[index++] = hexDigits[b & 0xf]; 203 | } 204 | // 字符数组组合成字符串返回 205 | return new String(resultCharArray); 206 | 207 | } 208 | 209 | private void consoleLog(String msg) 210 | { 211 | System.out.println("[info]: "+msg); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/pojo/Request.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 7 | * @author huisong 8 | * 9 | */ 10 | public class Request implements Serializable { 11 | 12 | /** 13 | * 14 | */ 15 | private static final long serialVersionUID = 1L; 16 | 17 | 18 | public int getActionCode() { 19 | return actionCode; 20 | } 21 | 22 | public void setActionCode(int actionCode) { 23 | this.actionCode = actionCode; 24 | } 25 | 26 | public String getIp() { 27 | return ip; 28 | } 29 | 30 | public void setIp(String ip) { 31 | this.ip = ip; 32 | } 33 | 34 | public byte[] getData() { 35 | return data; 36 | } 37 | 38 | public void setData(byte[] data) { 39 | this.data = data; 40 | } 41 | 42 | int actionCode; 43 | 44 | String reqMes; 45 | 46 | public String getReqMes() { 47 | return reqMes; 48 | } 49 | 50 | public void setReqMes(String reqMes) { 51 | this.reqMes = reqMes; 52 | } 53 | 54 | String ip; 55 | 56 | byte[] data; 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/pojo/Response.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 7 | * @author huisong 8 | * 9 | */ 10 | public class Response implements Serializable { 11 | 12 | /** 13 | * 14 | */ 15 | private static final long serialVersionUID = 1L; 16 | 17 | Request req; 18 | 19 | String resMessage; 20 | 21 | String resType; 22 | 23 | String resCode; 24 | 25 | public String getResType() { 26 | return resType; 27 | } 28 | 29 | public void setResType(String resType) { 30 | this.resType = resType; 31 | } 32 | 33 | Object deleteFiles; 34 | 35 | Object newFiles; 36 | 37 | Object overrideFiles; 38 | 39 | public String getResCode() { 40 | return resCode; 41 | } 42 | 43 | public void setResCode(String resCode) { 44 | this.resCode = resCode; 45 | } 46 | 47 | public Object getDeleteFiles() { 48 | return deleteFiles; 49 | } 50 | 51 | public void setDeleteFiles(Object deleteFiles) { 52 | this.deleteFiles = deleteFiles; 53 | } 54 | 55 | public Object getNewFiles() { 56 | return newFiles; 57 | } 58 | 59 | public void setNewFiles(Object newFiles) { 60 | this.newFiles = newFiles; 61 | } 62 | 63 | public Object getOverrideFiles() { 64 | return overrideFiles; 65 | } 66 | 67 | public void setOverrideFiles(Object overrideFiles) { 68 | this.overrideFiles = overrideFiles; 69 | } 70 | 71 | public byte[] data; 72 | 73 | public Request getReq() { 74 | return req; 75 | } 76 | 77 | public void setReq(Request req) { 78 | this.req = req; 79 | } 80 | 81 | public String getResMessage() { 82 | return resMessage; 83 | } 84 | 85 | public void setResMessage(String resMessage) { 86 | this.resMessage = resMessage; 87 | } 88 | 89 | public byte[] getData() { 90 | return data; 91 | } 92 | 93 | public void setData(byte[] data) { 94 | this.data = data; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/server/Server.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.server; 2 | 3 | 4 | 5 | import com.yhs.fileserver.common.MarshallingCodeCFactory; 6 | import com.yhs.fileserver.core.FileScan; 7 | 8 | import io.netty.bootstrap.ServerBootstrap; 9 | import io.netty.channel.ChannelFuture; 10 | import io.netty.channel.ChannelHandler; 11 | import io.netty.channel.ChannelInitializer; 12 | import io.netty.channel.ChannelOption; 13 | import io.netty.channel.EventLoopGroup; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.SocketChannel; 16 | import io.netty.channel.socket.nio.NioServerSocketChannel; 17 | import io.netty.handler.logging.LogLevel; 18 | import io.netty.handler.logging.LoggingHandler; 19 | import io.netty.handler.stream.ChunkedWriteHandler; 20 | 21 | /** 22 | * 23 | * @author huisong 24 | * 25 | */ 26 | public class Server { 27 | 28 | public static ChannelHandler marshallingEncoderCache; 29 | 30 | public static void main(String[] args) throws Exception { 31 | int port = 9999; 32 | if (args != null && args.length > 0) { 33 | try { 34 | port = Integer.valueOf(args[0]); 35 | } catch (NumberFormatException e) { 36 | } 37 | } 38 | FileScan.getDefault().scanningAndWrite("md5.record"); 39 | new Server().bind(port); 40 | } 41 | 42 | public void bind(int port) throws InterruptedException { 43 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 44 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 45 | 46 | try { 47 | marshallingEncoderCache = MarshallingCodeCFactory.buildMarshallingEncoder(); 48 | 49 | ServerBootstrap b = new ServerBootstrap(); 50 | b.group(bossGroup, workerGroup) 51 | .channel(NioServerSocketChannel.class) 52 | .option(ChannelOption.SO_BACKLOG, 100) 53 | .handler(new LoggingHandler(LogLevel.ERROR)) 54 | .childHandler(new ChannelInitializer() { 55 | 56 | @Override 57 | protected void initChannel(SocketChannel ch) 58 | throws Exception { 59 | 60 | ch.pipeline().addLast("marencoder",marshallingEncoderCache); 61 | ch.pipeline().addLast( 62 | MarshallingCodeCFactory 63 | .buildMarshallingDecoder()); 64 | ch.pipeline().addLast("chunkedWriteHandler",new ChunkedWriteHandler()); 65 | ch.pipeline().addLast("ServerHandler",new ServerHandler()); 66 | 67 | } 68 | }); 69 | 70 | ChannelFuture f = b.bind(port).sync(); 71 | 72 | f.channel().closeFuture().sync(); 73 | 74 | } finally { 75 | bossGroup.shutdownGracefully(); 76 | workerGroup.shutdownGracefully(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/yhs/fileserver/server/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.yhs.fileserver.server; 2 | 3 | import java.io.RandomAccessFile; 4 | 5 | import com.yhs.fileserver.common.Constant; 6 | import com.yhs.fileserver.common.FileUtil; 7 | import com.yhs.fileserver.core.CompareServer; 8 | import com.yhs.fileserver.core.FileScan; 9 | import com.yhs.fileserver.pojo.Request; 10 | import com.yhs.fileserver.pojo.Response; 11 | 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.channel.Channel; 14 | import io.netty.channel.ChannelFuture; 15 | import io.netty.channel.ChannelHandlerAdapter; 16 | import io.netty.channel.ChannelHandlerContext; 17 | import io.netty.channel.ChannelProgressiveFuture; 18 | import io.netty.channel.ChannelProgressiveFutureListener; 19 | import io.netty.channel.DefaultFileRegion; 20 | import io.netty.util.CharsetUtil; 21 | 22 | /** 23 | * 24 | * @author huisong 25 | * 26 | */ 27 | public class ServerHandler extends ChannelHandlerAdapter { 28 | 29 | @Override 30 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 31 | super.channelReadComplete(ctx); 32 | } 33 | 34 | /** 35 | * 写入文件结尾符 36 | * @param channel 37 | */ 38 | private void writeContenEnd(Channel channel) { 39 | ByteBuf buffer = channel.alloc().buffer(6); 40 | byte[] buf = new String(Constant.FILE_SEPARATOR).getBytes(CharsetUtil.UTF_8); 41 | buffer.writeBytes(buf); 42 | channel.pipeline().writeAndFlush(buffer); 43 | channel.pipeline().addBefore("chunkedWriteHandler", "marencoder", Server.marshallingEncoderCache); 44 | } 45 | 46 | 47 | 48 | @Override 49 | public void channelRead(ChannelHandlerContext ctx, Object msg) 50 | throws Exception { 51 | Request req = (Request) msg; 52 | consoleLog("请求类型 ["+req.getActionCode() + "] ip [" + req.getIp()+"]"); 53 | int code = req.getActionCode(); 54 | 55 | switch (code) { 56 | //差异列表 57 | case 0x001: 58 | byte[] bytes = req.getData(); 59 | String md5Name = Constant.clientMd5 + ctx.channel().id(); 60 | FileUtil.writeFile(md5Name, bytes); 61 | CompareServer compare = new CompareServer(md5Name,Constant.serverMd5); 62 | compare.compare(); 63 | FileUtil.deleteFile(md5Name); 64 | Response res = new Response(); 65 | res.setDeleteFiles(compare.getDeleteList()); 66 | res.setNewFiles(compare.getNewList()); 67 | res.setOverrideFiles(compare.getOverrideList()); 68 | res.setResCode(Constant.SuccessCode); 69 | res.setResMessage("成功获取文件差异列表"); 70 | res.setResType(Constant.TYPE_GET_DIFFERENT); 71 | ctx.writeAndFlush(res); 72 | break; 73 | 74 | //文件大小 75 | case 0x006: 76 | String name = FileScan.rootDirPath + req.getReqMes(); 77 | RandomAccessFile raf0 = new RandomAccessFile(name,"r"); 78 | long size = raf0.length(); 79 | raf0.close(); 80 | Response res1 = new Response(); 81 | res1.setReq(req); 82 | res1.setResCode(Constant.SuccessCode); 83 | res1.setResType(Constant.TYPE_GET_SIZE); 84 | res1.setResMessage(String.valueOf(size)); 85 | ctx.writeAndFlush(res1); 86 | break; 87 | 88 | //文件下载 89 | case 0x002: 90 | 91 | ctx.pipeline().remove("marencoder"); 92 | String fileName = FileScan.rootDirPath + req.getReqMes(); 93 | final RandomAccessFile raf = new RandomAccessFile(fileName,"r"); 94 | 95 | long count = raf.length(); 96 | if(count == 0) 97 | { 98 | consoleLog("文件长度为0-->"+fileName); 99 | writeContenEnd(ctx.channel()); 100 | return; 101 | } 102 | //zero-copy 103 | ChannelFuture future = ctx.writeAndFlush(new DefaultFileRegion(raf.getChannel(), 0, raf.length()), 104 | ctx.newProgressivePromise()); 105 | 106 | future.addListener(new ChannelProgressiveFutureListener() { 107 | 108 | public void operationComplete(ChannelProgressiveFuture future) 109 | throws Exception { 110 | future.channel().pipeline().addBefore("chunkedWriteHandler", "marencoder", Server.marshallingEncoderCache); 111 | raf.close(); 112 | } 113 | 114 | public void operationProgressed(ChannelProgressiveFuture future, 115 | long progress, long total) throws Exception { 116 | 117 | } 118 | }); 119 | break; 120 | default: 121 | break; 122 | } 123 | } 124 | 125 | private void consoleLog(String msg) 126 | { 127 | System.out.println("[info]:"+msg); 128 | } 129 | 130 | @Override 131 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 132 | cause.printStackTrace(); 133 | ctx.close();// 发生异常,关闭链路 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/com/yhs/autoupdate/autoupdate/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.yhs.autoupdate.autoupdate; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | --------------------------------------------------------------------------------