9

背景(您可以跳过此部分)

我有大量数据(大约 3 mb)需要在数百台机器上保持最新。有些机器运行 C#,有些运行 Java。数据可能随时更改,需要在几分钟内传播给客户端。数据以 Json 格式从 4 个负载平衡服务器传送。这 4 台服务器运行带有 Mvc 3 和 C# 4.0 的 ASP.NET 4.0。

在 4 个服务器上运行的代码有一个散列算法,该算法对 Json 响应进行散列,然后将散列转换为字符串。这个哈希是给客户端的。然后,每隔几分钟,客户端会使用哈希 ping 服务器,如果哈希过期,则返回新的 Json 对象。如果散列仍然是当前的,则返回带有空正文的 304。

有时 4 个框生成的哈希值在各个框之间不一致,这意味着客户端不断下载数据(每个请求可能会访问不同的服务器)。

代码片段

这是用于生成哈希的代码。

internal static HashAlgorithm Hasher { get; set; }
...
Hasher = new SHA1Managed();
...
Convert.ToBase64String(Hasher.ComputeHash(Encoding.ASCII.GetBytes(jsonString)));

为了尝试调试问题,我将其拆分如下:

Prehash = PreHashBuilder.ToString();
ASCIIBytes = Encoding.ASCII.GetBytes(Prehash);
HashedBytes = Hasher.ComputeHash(ASCIIBytes);
Hash = Convert.ToBase64String(HashedBytes);

然后我添加了一条吐出上述值的路线,并使用 Beyond Compare 来比较差异。

字节数组被转换为字符串格式以供 BeyondCompare 使用,方法是使用:

private static string GetString(byte[] bytes)
{
    StringBuilder sb = new StringBuilder();
    foreach (byte b in bytes)
    {
        sb.Append(b);
    }
    return sb.ToString();
} 

如您所见,字节数组按字面意思显示为字节序列。它没有被“转换”。

问题

我发现 Prehash 和 ASCIIBytes 的值是相同的,但是 HashedBytes 的值是不同的——这意味着 Hash 也是不同的。

我在 4 个服务器上重启了 IIS 网站几次,当它们有不同的哈希值时,比较了 BeyondCompare 中的值。在任何情况下,都是“HashedBytes”值不同(SHA1Managed.ComputeHash(...) 的结果)

问题

我究竟做错了什么?ComputeHash 函数的输入是相同的。SHA1Managed 机器是否依赖?这不会因为 4 台机器有一半的时间具有相同的哈希值。

我搜索了 StackOverFlow 和 Bing,但找不到其他有此问题的人。我能找到的最接近的是编码有问题的人,但我想我已经证明编码不是问题。

输出

我希望不要因为时间太长而将所有内容都倾倒在这里,但这是我正在比较的转储的片段:

哈希:o1ZxBaVuU6OhE6De96wJXUvmz3M=
HashedBytes:163861135165110831631611916022224717299375230207115
ASCIIBytes:.... Prehash:...

当我比较不同服务器上的两个页面时,ASCII 字节是相同的,但 HashedBytes 不是。我用于字节的转储方法不进行任何转换,它只是按顺序转储每个字节。我可以用“。”分隔字节。我想。

跟进 我已更改 b.ToString(CultureInfo.InvariantCulture) 并将 HashAlgorithm 设为局部变量而不是静态属性。我正在等待代码部署到服务器。

4

3 回答 3

13

我一直在尝试复制这个问题,但是一旦我将 SHA1Managed 属性设置为局部变量而不是全局静态变量,我就无法这样做。

问题在于多线程。我的代码是线程安全的,除了我标记为静态的 SHA1Managed 类。我假设 SHA1Managed.ComputeHash 在下面是线程安全的,但如果标记为内部静态,显然它不是。

重复一遍,如果标记为内部静态,则 SHA1Managed.ComputeHash 不是线程安全的。

MSDN 状态:

Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

我不知道为什么内部静态的行为与公共静态不同。

我会将@pst 标记为答案并添加评论以澄清问题,但@pst 发表了评论,因此我无法将其标记为答案。

感谢您的输入。

于 2012-09-28T18:36:33.313 回答
0

问题是您的代码可能与前导 0 混淆,请使用以下数组作为字符串代码进行比较。它将产生可靠的结果,并且专门设计用于将字节数组转换为字符串,以便它们可以在机器之间传输。

using System.Runtime.Remoting.Metadata.W3cXsd2001;

public byte[] StringToBytes(string value)
{
    SoapHexBinary soapHexBinary = SoapHexBinary.Parse(value);
    return soapHexBinary.Value;
}

public string BytesToString(byte[] value)
{
    SoapHexBinary soapHexBinary = new SoapHexBinary(value);
    return soapHexBinary.ToString();
}

此外,我建议您检查 JSON 是否存在细微差别,因为这会产生完全不同的散列。例如,某些文化将数字“一千六百点七”表示为1,600.71 000.7或什至1 600,7(请参阅此 Wikipedia页面)。

于 2012-09-28T17:56:30.120 回答
0

您的 GetString 方法可能会在不同文化的机器上产生不同的结果,因为 StringBuilder.Append(byte) 调用 byte.ToString(CultureInfo.CurrentCulture)。尝试

private static string GetString(byte[] bytes)
{
    StringBuilder sb = new StringBuilder();
    foreach (byte b in bytes)
    {
        sb.Append(b.ToString(CultureInfo.InvariantCulture));
    }
    return sb.ToString();
} 

但是使用不使用字节值的十进制字符串表示的方法会更好。

于 2012-09-28T17:40:59.090 回答