2

我是一名 MMORPG 私人服务器开发人员,我期待为用户的客户端创建一个多合一的更新程序,因为使用必须手动下载的补丁非常烦人和蹩脚。

我是 C# 的新手,但我已经成功地使用自己的界面和基本的游戏开始/选项按钮制作了自己的启动器,并注意从我的网络服务器读取。

现在,我想为此制作一个集成的更新功能,但我很迷茫,我不知道从哪里开始。这就是它的样子,它只是一个概念

在此处输入图像描述

它将具有用于启动游戏和更新它的主按钮,基本上,当您打开程序时,该按钮将写入“更新”并被禁用(当它搜索新更新时),如果找到,它会转进入一个可点击的按钮,然后在下载更新后它会自动变为“开始游戏”。

一个用于整体更新的进度条,另一个用于查看当前正在下载的文件的进度,所有这些都包含基本信息,如百分比和需要下载多少文件。

我需要找到一种方法让启动器通过 HTTP 方法检查网络服务器上的文件,并检查它们是否与客户端或更新的文件相同,因此它总是重新下载已经相同版本的文件以及一种方法,以便更新程序将下载更新为压缩存档,并在下载完成后自动提取和覆盖现有文件。

注意:正在更新的文件不是 .exe,它们主要是纹理/配置文件/地图/图像/等...

4

2 回答 2

4

我将为这个系统绘制一个可能的架构。它不完整,您应该将其视为前半部分的详细伪 C# 代码形式和后半部分的一组提示和建议。

我相信您可能需要两个应用程序:

  • AC# WinForms 客户端。
  • AC# 服务器端应用程序,可能是 Web 服务。

我不会关注这个答案的安全问题,但它们显然非常重要。我希望可以在更高级别上实现安全性,也许可以使用 SSL。Web 服务将在 IIS 中运行,实现某种形式的安全性应该主要是配置问题。

服务器端部分不是严格要求的,特别是如果您不想要压缩;可能有一种方法可以配置您的服务器,以便在website.com/updater发出 HTTP 请求时返回一个易于解析的文件列表。然而,拥有 Web 服务更灵活,而且可能更容易实现。您可以从查看这篇 MSDN 文章开始。如果您确实想要压缩,您可以将服务器配置为透明地压缩单个文件。我将尝试绘制所有可能的变体。

在单个更新 ZIP 文件的情况下,基本上更新程序 Web 服务应该能够响应两个不同的请求;首先,它可以返回相对于服务器目录website.com/updater的所有游戏文件的列表,以及它们的最后写入时间戳(GetUpdateInfoWeb 服务中的方法)。客户端会将这个列表与本地文件进行比较;服务器上可能不再存在某些文件(可能客户端必须删除本地副本),客户端上可能不存在某些文件(它们是全新的内容),并且客户端和服务器上可能同时存在一些其他文件,在这种情况下,客户端需要检查最后一次写入时间以确定它是否需要更新版本。客户端将构建这些文件相对于游戏内容目录的路径列表。游戏内容目录应镜像服务器website.com/updater目录。

其次,客户端将此列表发送到服务器( Web 服务中的GetUpdateURL)。服务器将创建一个包含更新的 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;
    }
}

客户端将使用HttpWebRequestHttpWebResponse对象下载 ZIP 文件。要更新进度条(在此设置中您只有一个进度条,请查看我对您问题的评论),您需要创建一个BackgroundWorker本文另一篇文章涵盖了相关方面(不幸的是,该示例是用 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 服务。

如果你想单独压缩你的文件,但你不能让服务器自动实现这个功能,你应该用这个接口定义一个 web 服务:

[ServiceContract]
public interface IUpdater
{
    [OperationContract]
    public FileModified[] GetUpdateInfo();

    [OperationContract]
    public string CompressFileAndGetURL(string path);
}

您可以在其中要求服务器压缩特定文件并返回压缩的单文件存档的 URL。

编辑 - 重要

特别是在你的更新非常频繁的情况下,你需要特别注意时区

编辑 - 另一种选择

我应该重申,这里的主要问题之一是从服务器获取当前版本中的文件列表;该文件应包括每个文件的最后写入时间。像 Apache 这样的服务器可以免费提供这样的列表,虽然它通常是供人类使用的,但是它很容易被程序解析。我确信必须有一些脚本/扩展名才能以更机器友好的方式格式化该列表。

还有另一种获取该列表的方法;你可以在服务器上有一个文本文件,对于每个游戏内容文件,它会存储它的最后写入时间,或者甚至更好的是,一个渐进的版本号。您将比较版本号而不是日期来检查您需要哪些文件。这将使您免受时区问题的影响。但是,在这种情况下,您需要维护此列表的本地副本,因为文件没有版本号之类的东西,而只有一个名称和一组日期。

于 2013-02-15T05:06:00.983 回答
1

这是一个广泛而多样的问题,根据不同的实施要求,有几个可以称为“正确”的答案。这里有一些想法......

  1. 我的方法是使用System.Security.Cryptography.SHA1为每个游戏资产生成哈希码列表。然后更新程序可以下载列表,将其与本地文件系统进行比较(缓存本地生成的哈希以提高效率)并构建要下载的新/更改文件列表。

  2. 如果游戏数据使用存档,则该过程会涉及更多,因为当内部可能只有一个小文件已更改时,您不希望下载巨大的存档。在这种情况下,您希望对存档中的每个文件进行哈希处理并提供一种下载这些包含文件的方法,然后使用您从服务器下载的文件更新存档。

  3. 最后,考虑一下使用 Binary Diff/Patch 算法通过下载更小的补丁文件来减少带宽需求。在这种情况下,客户端将请求从当前版本更新到文件最新版本的补丁,发送本地文件的哈希,以便服务器知道要发送哪个补丁。这要求您在服务器上为您希望能够修补的每个先前版本维护一堆补丁,这可能比您感兴趣的要多。

以下是一些可能相关的链接:

哦,考虑使用多部分下载器来更好地使客户端的可用带宽饱和。这会导致服务器上的负载更高,但可以大大改善客户端体验。

于 2013-02-15T03:54:39.410 回答