我会为这个系统绘制一个可能的架构。这是不完整的,你应该认为它是上半部分的一个详细的伪C#代码的形式,以及第二部分的一组提示和建议。
我相信你可能需要为这个两个应用:
- A C#的WinForms客户端。
- C#服务器端应用程序,也许是一个Web服务。
我不会关注这个答案的安全问题,但它们显然非常重要。我希望安全可以在更高层次上实现,可能使用SSL。 Web服务将在IIS中运行,并且实现某种形式的安全性应该主要是配置问题。
服务器端的部分不是严格要求的,特别是如果你不想压缩;可能有一种方法来配置您的服务器,以便在website.com/updater发出HTTP请求时返回一个可轻松解析的文件列表。但是,拥有Web服务会更加灵活,并且可能更容易实现。您可以先看看this MSDN article。如果您确实需要压缩,则可以将服务器配置为透明地压缩单个文件。我会尽力勾画出所有可能的变体。
在单个更新ZIP文件的情况下,基本上更新程序Web服务应该能够回答两个不同的请求;首先,它可以返回与服务器目录website.com/updater相关的所有游戏文件的列表以及它们的最后写入时间戳(在Web服务中的GetUpdateInfo方法)。客户端会将此列表与本地文件进行比较;某些文件可能不再存在于服务器上(可能客户端必须删除本地副本),有些文件可能不存在于客户端(它们是全新的内容),并且客户端和服务器上可能存在其他一些文件,在这种情况下,客户端需要检查最后写入时间以确定是否需要更新版本。客户端将建立这些文件相对于游戏内容目录的路径列表。游戏内容目录应镜像服务器website.com/updater目录。
其次,客户端将此列表发送到服务器(GetUpdateURL在Web服务中)。服务器将创建一个包含更新的ZIP并回复其URL。
[ServiceContract]
public interface IUpdater
{
[OperationContract]
public FileModified[] GetUpdateInfo();
[OperationContract]
public string GetUpdateURL();
}
[DataContract]
public class FileModified
{
[DataMember]
public string Path;
[DataMember]
public DateTime Modified;
}
public class Updater : IUpdater
{
public FileModified[] GetUpdateInfo()
{
// Get the physical directory
string updateDir = HostingEnvironment.MapPath("website.com/updater");
IList<FileModified> updateInfo = new List<FileModified>();
foreach (string path in Directory.GetFiles(updateDir))
{
FileModified fm = new FileModified();
fm.Path = // You may need to adjust path so that it is local with respect to updateDir
fm.Modified = new FileInfo(path).LastWriteTime;
updateInfo.Add(fm);
}
return updateInfo.ToArray();
}
[OperationContract]
public string GetUpdateURL(string[] files)
{
// You could use System.IO.Compression.ZipArchive and its
// method CreateEntryFromFile. You create a ZipArchive by
// calling ZipFile.Open. The name of the file should probably
// be unique for the update session, to avoid that two concurrent
// updates from different clients will conflict. You could also
// cache the ZIP packages you create, in a way that if a future
// update requires the same exact file you would return the same
// ZIP.
// You have to return the URL of the ZIP, not its local path on the
// server. There may be several ways to do this, and they tend to
// depend on the server configuration.
return urlOfTheUpdate;
}
}
客户端将通过使用HttpWebRequest的和HttpWebResponse对象下载ZIP文件。要更新进度条(在此设置中只有一个进度条,请查看我对您问题的评论),您需要创建一个BackgroundWorker。 This article和this other article涵盖了相关方面(不幸的是这个例子是用VB.NET编写的,但看起来与C#中的非常相似)。要前进,你需要让你多少字节收到的轨道进度条:
int nTotalRead = 0;
HttpWebRequest theRequest;
HttpWebResponse theResponse;
...
byte[] readBytes = new byte[1024];
int bytesRead = theResponse.GetResponseStream.Read(readBytes, 0, 4096);
nTotalRead += bytesread;
int percent = (int)((nTotalRead * 100.0)/length);
一旦收到文件,你可以使用System.IO.Compression.ZipArchive.ExtractToDirectory更新您的游戏。
如果您不想使用.NET显式压缩文件,仍然可以使用Web服务的第一种方法获取更新文件的列表,然后使用将您需要的文件复制到客户端上HttpWebRequest/HttpWebResponse每对。这样你实际上可以有两个进度条。计数文件中的一个将被简单地设定为像百分比:
int filesPercent = (int)((nCurrentFile * 100.0)/nTotalFiles);
如果你有另一种方式来获得列表,你甚至不需要的网络服务。
如果要单独压缩文件,但你不能有这个功能由服务器自动实现的,你应该定义Web服务使用这个接口:
[ServiceContract]
public interface IUpdater
{
[OperationContract]
public FileModified[] GetUpdateInfo();
[OperationContract]
public string CompressFileAndGetURL(string path);
}
在这你可以问服务器压缩特定文件并返回压缩的单个文件归档的URL。
编辑 - 重要
尤其是在你的更新非常频繁的情况下,就需要特别注意时区。
编辑 - 另类
我要重申的主要问题是在这里获得的当前版本文件从服务器列表中的一个;这个文件应该包含每个文件的最后写入时间。像Apache这样的服务器可以免费提供这样一个列表,不过通常它可以用于人类消费,但是它仍然可以通过程序轻松解析。我确定必须有一些脚本/扩展名才能让这个列表以更加机器友好的方式进行格式化。
还有另一种方法来获得该列表;您可以在服务器上拥有一个文本文件,对于每个游戏内容文件,都会存储其最后一次写入时间,或者甚至可能更好的是存储渐进版本号。您可以比较版本号而不是日期来检查您需要的文件。这将防止您的时区问题。但是,在这种情况下,您需要维护此列表的本地副本,因为文件没有发行号,但只有名称和一组日期。
请不要通过HTTP下载可执行文件,然后运行它们。HTTP对MitM来说非常简单。 – SecurityMatt 2013-02-15 03:20:19
不,更新的文件是纹理,三维模型和其他类型的东西,即使有,它也会被压缩。 – Dan 2013-02-15 03:22:09
@ user2030800:被压缩不会防止攻击。你需要加密 - https或公钥加密。 – 2013-02-15 03:29:13