您的位置:首页 > 理论基础 > 计算机网络

网络游戏资源代码热更新开发方案

2015-04-16 10:43 197 查看
说是方案不如说 一个没有成熟的想法。

客户端这边没什么逻辑 就是下载 文件 如果是压缩文件解压 放到相应的目录。

关键的逻辑 就是 每次更新都有哪些文件 哪些资源修改 删除 新增 ,我是这么实现的 记录每个版本 中每个文件的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();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐