网络游戏资源代码热更新开发方案
2015-04-16 10:43
239 查看
说是方案不如说 一个没有成熟的想法。
客户端这边没什么逻辑 就是下载 文件 如果是压缩文件解压 放到相应的目录。
关键的逻辑 就是 每次更新都有哪些文件 哪些资源修改 删除 新增 ,我是这么实现的 记录每个版本 中每个文件的MD5值 然后进行每次判断。
说下我的想法,一个脚本或者一个程序 没执行一次 算是生成一个新的版本 。每次执行 记录每个文件的MD5值 存入本地数据库中 我用的是sqlite 方便快捷 没执行一次 生成一个version_xxx 表 xxx 为版本号 。
1、执行多次 产生多个表 然后写sql语句 判断 最新的表和之前的表中的文件名还MD5有什么不同。 找出不同 的文件名 写入一个文件中 或者是将新增 更新文件复制到 version_xxx 文件夹下 将删除的文件 文件名 写到一个单独的文件中
2、判断不同后直接将新增 更新文件和删除文件 分别写到一个version.txt文件中
以上两种情况 也是我不知道用哪个好,
第一种 我可以把新增文件和更新文件 打包到一个包中 压缩 然后供下载 优点 客户端下载时可以下载一个文件 下载一个大包 比下载多个散列文件 方便速度快。 还有一点 就是有个人跟我说过 在游戏维护时 和版本多时 多则200个版本 同时维护会产生 200个version_xxx 上传到cdn 上很慢。维护不方便。
第二种 直接下载一个version.txt 读取内容判断哪些文件需要下载哪些需要删除 每个新增文件都可以开一个线程去下载 。没什么需要维护的 只需将version_xxx 传到cdn上就可以了 。不用浪费存储空间 。
各有优缺点 但是综合来说还是第二个方案比较好。
下面我将代码贴出来。
客户端这边没什么逻辑 就是下载 文件 如果是压缩文件解压 放到相应的目录。
关键的逻辑 就是 每次更新都有哪些文件 哪些资源修改 删除 新增 ,我是这么实现的 记录每个版本 中每个文件的MD5值 然后进行每次判断。
说下我的想法,一个脚本或者一个程序 没执行一次 算是生成一个新的版本 。每次执行 记录每个文件的MD5值 存入本地数据库中 我用的是sqlite 方便快捷 没执行一次 生成一个version_xxx 表 xxx 为版本号 。
1、执行多次 产生多个表 然后写sql语句 判断 最新的表和之前的表中的文件名还MD5有什么不同。 找出不同 的文件名 写入一个文件中 或者是将新增 更新文件复制到 version_xxx 文件夹下 将删除的文件 文件名 写到一个单独的文件中
2、判断不同后直接将新增 更新文件和删除文件 分别写到一个version.txt文件中
以上两种情况 也是我不知道用哪个好,
第一种 我可以把新增文件和更新文件 打包到一个包中 压缩 然后供下载 优点 客户端下载时可以下载一个文件 下载一个大包 比下载多个散列文件 方便速度快。 还有一点 就是有个人跟我说过 在游戏维护时 和版本多时 多则200个版本 同时维护会产生 200个version_xxx 上传到cdn 上很慢。维护不方便。
第二种 直接下载一个version.txt 读取内容判断哪些文件需要下载哪些需要删除 每个新增文件都可以开一个线程去下载 。没什么需要维护的 只需将version_xxx 传到cdn上就可以了 。不用浪费存储空间 。
各有优缺点 但是综合来说还是第二个方案比较好。
下面我将代码贴出来。
package com.pigsns; package com.pigsns; import java.io.File; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List; public class ResourcesVersion { public static String CommPath="/Users/liuxy/Documents/workspace/bqsg2"; public static String DBpath = CommPath; public static String Resourcepath = CommPath + "/bqsg"; public static String Publishpath = CommPath + "/version/publish/ios"; public static void main(String[] args) { // TODO Auto-generated method stub List pathList = new ArrayList(); pathList.add(CommPath + "/bqsg/data"); pathList.add(CommPath + "/bqsg/font"); pathList.add(CommPath + "/bqsg/Shaders"); pathList.add(CommPath + "/bqsg/sound"); pathList.add(CommPath + "/bqsg/UI"); ResourcesVersion resourcesUtil = new ResourcesVersion(); resourcesUtil.dealResources(pathList); } public void dealResources(List pathList) { // 删除目标文件中的所有文件 FileUtils fileUtils = new FileUtils(); fileUtils.delAllFile(Publishpath); try { // 连接SQLite的JDBC Class.forName("org.sqlite.JDBC"); // 建立一个数据库名zieckey.db的连接,如果不存在就在当前目录下创建之 Connection conn = DriverManager.getConnection("jdbc:sqlite:" + DBpath + "/database.db"); Statement stat = conn.createStatement(); stat.executeUpdate(String .format("create table if not exists version (name char(256),version integer,versionMin integer);"));// 创建一个表,两列 // 查找现有的版本号 ResultSet rs = stat .executeQuery("SELECT * FROM version ORDER BY version DESC limit 1"); int version = 0; int versionMin = 0; while (rs.next()) { version = rs.getInt("version"); versionMin = rs.getInt("versionMin"); version++; } // 如果版本号为0 说明没有版本文件数据 新增版本数据 if (version == 0) { String sql = "insert into version values('1.0.0',0,1)"; int r = stat.executeUpdate(sql); if (r == 0) { System.out.println("插入version失败"); return; } version++; versionMin++; } // 创建新的版本表 stat.executeUpdate(String .format("create table if not exists file_version_%d (file_name char(256) NOT NULL PRIMARY KEY,file_md5 varchar(33) NOT NULL);", version));// 创建一个表,两列 // 清空新的版本表 stat.executeUpdate(String.format("delete from file_version_%d", version)); String sql = "insert into file_version_" + version + " values(?,?)"; PreparedStatement ps = conn.prepareStatement(sql); int result = 0; for (int i = 0; i < pathList.size(); i++) { // 计算文件MD5 并保存到数据表中 result = fileUtils.initFilesMd5(pathList.get(i), Resourcepath + "/", ps); if (result == 0) { break; } } if (result == 1) { // 更新一个新的版本号 +1 String sql2 = "update version set version = " + version; int r = stat.executeUpdate(sql2); if (r == 0) { System.out.println("更新版本号失败"); } } // 循环所有强制更新版本之后的数据 for (int i = versionMin; i < version; i++) { // 计算文件差异性 并生成增量文件 便于更新 String sqlAddUp = "select * ,1 from file_version_" + (version) + " as a where not exists(select file_name from file_version_" + i + " as b where b.file_name=a.file_name)" // 新增更新 + " UNION " + "select * ,1 from file_version_" + (version) + " as a where not exists(select file_md5 from file_version_" + i + " as b where b.file_md5=a.file_md5)";// 新增更新 ResultSet rs2 = stat.executeQuery(sqlAddUp); int index = 0; String versionPath = Publishpath + "/version_" + i; while (rs2.next()) { // 有不同的记录下。 if (index == 0) { System.out.println("\n+++++++++++++版本号为:" + i + "->" + version + "++++++++++++++++++\n"); index++; } File file = new File(versionPath); if (file != null && !file.exists()) { file.mkdirs(); } System.out.println(rs2.getString("file_name") + "++++++++"); fileUtils.copyFile( Resourcepath + "/" + rs2.getString("file_name"), versionPath + "/" + rs2.getString("file_name")); } // 删除的文件 String sqlDelete = "select * ,0 as state from file_version_" + i + " as a where not exists(select file_name from file_version_" + (version) + " as b where b.file_name=a.file_name)"; // 删除 ResultSet rsDelete = stat.executeQuery(sqlDelete); List updateList = new ArrayList(); while (rsDelete.next()) { // 有不同的记录下。 if (index == 0) { System.out.println("\n+++++++++++++版本号为:" + i + "->" + version + "++++++++++++++++++\n"); index++; } System.out.println(rs2.getString("file_name") + "--------"); // 写到一个文件中供前端下载 并执行删除操作 updateList.add(rs2.getString("file_name")); } if (updateList.size() != 0) { fileUtils.createAndUpateFile(versionPath + "/version", updateList); } if (index != 0) { // 压缩 ZipCompressor zc = new ZipCompressor(versionPath + ".zip"); zc.compress(versionPath); // 删除 包含自己 fileUtils.delFolder(versionPath); } } System.out.println("版本更新成功--版本号:" + version); conn.close(); // 结束数据库的连接 } catch (Exception e) { e.printStackTrace(); } System.out.println("over"); } }
package com.pigsns; package com.pigsns; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.InputStream; import java.io.PrintWriter; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.util.List; public class FileUtils { public int initFilesMd5(String path, String replacePath, PreparedStatement ps) { File[] files = new File(path).listFiles(); for (File f : files) { if (f.isDirectory() && f.getName().indexOf(".svn") == -1 && f.getName().indexOf(".") == -1 && f.getName().indexOf("..") == -1) { initFilesMd5(f.getAbsolutePath(), replacePath, ps); } else if (f.getName().indexOf("Thumbs.db") == -1 && f.getName().indexOf(".svn") == -1 && f.getName().indexOf(".DS_Store") == -1) { String dd = f.getAbsolutePath().replace(replacePath, ""); try { ps.setString(1, dd); ps.setString(2, MakeFileHash.getFileMD5(f.getAbsolutePath())); ps.executeUpdate(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); return 0; } // 插入数据 } } return 1; } public boolean createAndUpateFile(String strFilePath, List updateList) { boolean bFlag = true; try { File file = new File(strFilePath.toString()); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } if (!file.exists()) { bFlag = file.createNewFile(); } if (bFlag == Boolean.TRUE) { FileWriter fw = new FileWriter(file); PrintWriter pw = new PrintWriter(fw); for (int i = 0; i < updateList.size(); i++) { pw.println(updateList.get(i)); } pw.close(); } } catch (Exception e) { // logger.error("新建文件操作出错" + e.getLocalizedMessage()); e.printStackTrace(); return false; } return bFlag; } /** * 复制单个文件 * * @param oldPath * String 原文件路径 如:c:/fqf.txt * @param newPath * String 复制后路径 如:f:/fqf.txt * @return boolean */ public void copyFile(String oldPath, String newPath) { File file = new File(newPath); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } try { int byteread = 0; File oldfile = new File(oldPath); if (oldfile.exists()) { // 文件存在时 InputStream inStream = new FileInputStream(oldPath); // 读入原文件 FileOutputStream fs = new FileOutputStream(newPath); byte[] buffer = new byte[1444]; while ((byteread = inStream.read(buffer)) != -1) { fs.write(buffer, 0, byteread); } inStream.close(); fs.close(); } } catch (Exception e) { System.out.println("复制单个文件操作出错"); e.printStackTrace(); } } public void initFilesHashCode(String path, String replacePath, Statement stat) { File[] files = new File(path).listFiles(); for (File f : files) { if (f.isDirectory() && f.getName().indexOf(".svn") == -1) { initFilesHashCode(f.getAbsolutePath(), replacePath, stat); } else if (f.getName().indexOf("Thumbs.db") == -1 && f.getName().indexOf(".svn") == -1 && f.getName().indexOf(".DS_Store") == -1 && f.getName().indexOf(".") == -1 && f.getName().indexOf("..") == -1) { String dd = f.getAbsolutePath().replace(replacePath, ""); int hash0 = MakeStringHash.getHashCode(dd, 0); int hash1 = MakeStringHash.getHashCode(dd, 1); int hash2 = MakeStringHash.getHashCode(dd, 2); int result = 0; try { result = stat.executeUpdate("insert into file_info values(" + hash0 + "," + hash1 + "," + hash2 + ",'" + dd + "','" + MakeFileHash.getFileMD5(f.getAbsolutePath()) + "'," + 1 + ");"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 插入数据 System.out.println(result + "===" + dd); } } } public void initFilesContent(String path, String replacePath, PreparedStatement ps) throws Exception { File[] files = new File(path).listFiles(); for (File f : files) { if (f.isDirectory() && f.getName().indexOf(".svn") == -1) { initFilesContent(f.getAbsolutePath(), replacePath, ps); } else if (f.getName().indexOf("Thumbs.db") == -1 && f.getName().indexOf(".svn") == -1 && f.getName().indexOf(".DS_Store") == -1) { String dd = f.getAbsolutePath().replace(replacePath, ""); int hash0 = MakeStringHash.getHashCode(dd, 0); int hash1 = MakeStringHash.getHashCode(dd, 1); int hash2 = MakeStringHash.getHashCode(dd, 2); int result = 0; try { // 创建对应长度Byte数组: byte[] bytes = getBytesFromFile(f); ps.setInt(1, hash0); ps.setInt(2, hash1); ps.setInt(3, hash2); ps.setBytes(4, bytes); ps.setInt(5, (int) f.length()); result = ps.executeUpdate(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 插入数据 System.out.println(result + "+++++" + dd); } } } // 返回一个byte数组 public byte[] getBytesFromFile(File file) throws Exception { InputStream is = new FileInputStream(file); // 获取文件大小 long length = file.length(); if (length > Integer.MAX_VALUE) { // 文件太大,无法读取 } // 创建一个数据来保存文件数据 byte[] bytes = new byte[(int) length]; // 读取数据到byte数组中 int offset = 0; int numRead = 0; while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { offset += numRead; } // 确保所有数据均被读取 if (offset < bytes.length) { System.out.println("Could not completely read file " + file.getName()); } // Close the input stream and return bytes is.close(); return bytes; } /** * * @param folderPath * 包含删除自己 */ public void delFolder(String folderPath) { try { delAllFile(folderPath); // 删除完里面所有内容 String filePath = folderPath; filePath = filePath.toString(); java.io.File myFilePath = new java.io.File(filePath); myFilePath.delete(); // 删除空文件夹 } catch (Exception e) { e.printStackTrace(); } } /** * * @param path * 删除指定文件夹下所有文件 文件夹完整绝对路径 不包含自己 * @return */ public boolean delAllFile(String path) { boolean flag = false; File file = new File(path); if (!file.exists()) { return flag; } if (!file.isDirectory()) { return flag; } String[] tempList = file.list(); File temp = null; for (int i = 0; i < tempList.length; i++) { if (path.endsWith(File.separator)) { temp = new File(path + tempList[i]); } else { temp = new File(path + File.separator + tempList[i]); } if (temp.isFile()) { temp.delete(); } if (temp.isDirectory()) { delAllFile(path + "/" + tempList[i]);// 先删除文件夹里面的文件 delFolder(path + "/" + tempList[i]);// 再删除空文件夹 flag = true; } } return flag; } }
package com.pigsns; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.zip.CRC32; import java.util.zip.CheckedOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class ZipCompressor { static final int BUFFER = 8192; private File zipFile; public ZipCompressor(String pathName) { zipFile = new File(pathName); } public void compress(String srcPathName) { File file = new File(srcPathName); if (!file.exists()) throw new RuntimeException(srcPathName + "不存在!"); try { FileOutputStream fileOutputStream = new FileOutputStream(zipFile); CheckedOutputStream cos = new CheckedOutputStream(fileOutputStream, new CRC32()); ZipOutputStream out = new ZipOutputStream(cos); String basedir = ""; compress(file, out, basedir); out.close(); } catch (Exception e) { throw new RuntimeException(e); } } private void compress(File file, ZipOutputStream out, String basedir) { /* 判断是目录还是文件 */ if (file.isDirectory()) { this.compressDirectory(file, out, basedir); } else { this.compressFile(file, out, basedir); } } /** 压缩一个目录 */ private void compressDirectory(File dir, ZipOutputStream out, String basedir) { if (!dir.exists()) return; File[] files = dir.listFiles(); for (int i = 0; i < files.length; i++) { /* 递归 */ compress(files[i], out, basedir + dir.getName() + "/"); } } /** 压缩一个文件 */ private void compressFile(File file, ZipOutputStream out, String basedir) { if (!file.exists()) { return; } try { BufferedInputStream bis = new BufferedInputStream( new FileInputStream(file)); ZipEntry entry = new ZipEntry(basedir + file.getName()); out.putNextEntry(entry); int count; byte data[] = new byte[BUFFER]; while ((count = bis.read(data, 0, BUFFER)) != -1) { out.write(data, 0, count); } bis.close(); } catch (Exception e) { throw new RuntimeException(e); } } } package com.pigsns; import java.security.MessageDigest; public class MakeStringHash { private static String getHash(byte[] plainByte,String hashType) { try { MessageDigest md = MessageDigest.getInstance(hashType); md.update(plainByte); byte b[] = md.digest(); int i; StringBuffer buf = new StringBuffer(""); for (int offset = 0; offset < b.length; offset++) { i = b[offset]; if (i < 0) i += 256; if (i < 16) buf.append("0"); buf.append(Integer.toHexString(i)); } return buf.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } public static String getStringMD5(String filename) { String str = ""; try { str = getHash(filename.getBytes(), "MD5"); } catch(Exception e) { e.printStackTrace(); } return str; } public static String getStringSHA1(String filename) { String str = ""; try { str = getHash(filename.getBytes(), "SHA1"); } catch(Exception e) { e.printStackTrace(); } return str; } public static String getStringSHA256(String filename) { String str = ""; try { str = getHash(filename.getBytes(), "SHA-256"); } catch(Exception e) { e.printStackTrace(); } return str; } public static String getStringSHA384(String filename) { String str = ""; try { str = getHash(filename.getBytes(), "SHA-384"); } catch(Exception e) { e.printStackTrace(); } return str; } public static String getStringSHA512(String filename) { String str = ""; try { str = getHash(filename.getBytes(), "SHA-512"); } catch(Exception e) { e.printStackTrace(); } return str; } public static int getHashCode(String str,int h) { int off = 0; int len = str.length(); for (int i = 0; i < len; i++) { h = 31 * h + str.charAt(off++); } return h; } } package com.pigsns; import java.io.FileInputStream; import java.io.InputStream; import java.security.MessageDigest; public class MakeFileHash { private static char hexChar[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public MakeFileHash() { } public static String getFileMD5(String filename) { String str = ""; try { str = getHash(filename, "MD5"); } catch(Exception e) { e.printStackTrace(); } return str; } public static String getFileSHA1(String filename) { String str = ""; try { str = getHash(filename, "SHA1"); } catch(Exception e) { e.printStackTrace(); } return str; } public static String getFileSHA256(String filename) { String str = ""; try { str = getHash(filename, "SHA-256"); } catch(Exception e) { e.printStackTrace(); } return str; } public static String getFileSHA384(String filename) { String str = ""; try { str = getHash(filename, "SHA-384"); } catch(Exception e) { e.printStackTrace(); } return str; } public static String getFileSHA512(String filename) { String str = ""; try { str = getHash(filename, "SHA-512"); } catch(Exception e) { e.printStackTrace(); } return str; } private static String getHash(String fileName, String hashType) throws Exception { InputStream fis = new FileInputStream(fileName); byte buffer[] = new byte[1024]; MessageDigest md5 = MessageDigest.getInstance(hashType); for(int numRead = 0; (numRead = fis.read(buffer)) > 0;) { md5.update(buffer, 0, numRead); } fis.close(); return toHexString(md5.digest()); } private static String toHexString(byte b[]) { StringBuilder sb = new StringBuilder(b.length * 2); for(int i = 0; i < b.length; i++) { sb.append(hexChar[(b[i] & 0xf0) >>> 4]); sb.append(hexChar[b[i] & 0xf]); } return sb.toString(); } }
相关文章推荐
- 一、创建Assetbundle 在unity3d开发的游戏中,无论模型,音频,还是图片等,我们都做成Prefab,然后打包成Assetbundle,方便我们后面的使用,来达到资源的更新。
- Cocos2d塔防游戏开发]Cocos2dx-3.X完成塔防游戏《王国保卫战》--简介+代码+资源
- android游戏的增量更新(资源及代码的热更新)
- 网络游戏服务器开发::用模板偏特化封装C++调用lua的代码
- 图形、游戏物理 学习、开发、工具(来自网络的很多免费的好资源 )
- iOS开发网络资源整理-持续更新
- Cocos2d-x游戏开发之Mac下Android如何更新项目代码
- [Cocos2d塔防游戏开发]Cocos2dx-3.X完成塔防游戏《王国保卫战》--简介+代码+资源
- [Cocos2d塔防游戏开发]Cocos2dx-3.X完成塔防游戏《王国保卫战》--简介+代码+资源
- android游戏开发的框架设计!(已更新资源图片)
- 在游戏开发过程一个关于Xcode5不更新Resource的一个方案。
- 各大游戏公司面经笔试题汇总(资源来自网络)(实时更新)
- 直接用Socket TCP开发网络游戏(三)
- Unity3D游戏开发框架-资源管理类ResourceManage
- cocos2d-x商业级单机游戏和网络游戏开发系列
- Mac和iOS开发资源汇总—更新于2013-10-14
- 讲解游戏开发与项目下的hdpi 、mdpi与ldpi资源文件夹以及游戏高清版本的设置
- 2D网络游戏开发(网络篇)(二)
- 免费的游戏开发资源
- Android开发17——获取网络资源之XML数据