我正在开发一个WPF(WCF架构)应用程序。这个应用程序有2个主要的解决方案,客户端(前端)和控制器(后端),客户端与后端通信的方式是通过反射调用,本质上它建立了一个XML文档,包含要执行的方法,方法所在的命名空间和方法参数等信息。
所以这个具体的方法是上传文档。它可以完美地工作,直到文件大小> 1GB。当客户端为反射调用构建XML文档时,就会发生错误。
我们像这样添加到XML文档中:
result.Add(new XElement(itemProperty.Name, new XCData(data)));
字符串
在这种情况下,itemProperty.Name是方法名称,XCData是方法args。所以要上传的文档显然是参数,我们在byte[]中接收它。我们需要将它作为字符串传递给XCData构造函数,因此使用以下内容:
string data= Convert.ToBase64String((byte[])value)
型
请注意,这适用于较小的文件,但对于1GB的文件,当试图将byte[]转换为base64 String时,会抛出“System.OutOfMemoryException”异常。
我尝试过分块阅读数组,但是当调用stringBuilder.ToString()时,同样的异常被抛出.
public static string HandleBigByteArray(byte[] arr)
{
try
{
#region approach 2
int chunkSize = 104857600;//100MB
StringBuilder b64StringBuilder = new StringBuilder();
int offset = 0;
while (offset < arr.Length)
{
int remainingBytes = arr.Length - offset;
int bytesToEncode = Math.Min(chunkSize, remainingBytes);
string base64Chunk = Convert.ToBase64String(arr, offset, bytesToEncode);
b64StringBuilder.Append(base64Chunk);
offset += bytesToEncode;
}
return b64StringBuilder.ToString();
#endregion
}
catch (Exception)
{
throw;
}
}
型
我不知道该怎么做,也不知道如何进一步调试/处理这个问题。
1条答案
按热度按时间vnzz0bqm1#
这里的基本问题是,在构造Base64字符串时,当您执行
b64StringBuilder.ToString()
时,您试图超过系统上最大可能的.NET字符串长度,正如HitScan在this answer to * What is the maximum possible length of a .NET string? * 中所解释的那样,在64位系统上,最多只能有int.MaxValue / 2
个字符。为了进一步细分,您可以在.NET Framework上分配的最大连续内存块是
int.MaxValue
字节,即2GB。char
占用2个字节,而Base64编码将字符数增加了33%,因此您可以编码的最大字节数组大小约为3/4GB--这正是您所看到的。(Note如果您设置
gcAllowVeryLargeObjects
,您将能够在内存中分配最多4GB的数组,因此,如果您这样做,请将上述计算结果乘以系数2。)若要解决此特定问题,您可以建立一个
XText
序列,其中包含有限大小的部分区块,并将它们全部新增至itemProperty.Name
元素,而不是建立一个包含byte []
数组之整个Base64内容的单一XCData
。写入XML时,它们会格式化为单一连续文字值。为此,首先介绍以下扩展方法:
字符串
现在,假设您的
byte[] arr
值实际上是从fileName
文件中读取的,您可以执行以下操作:型
扩展方法
stream.ToBase64XTextChunks()
以块为单位读取流,并以块为单位进行编码,这样就不会达到最大数组大小或最大字符串长度。但如果内存中已经有了巨大的
byte [] arr
数组,则可以执行以下操作:型
注意到
Memory<T>
和ReadOnlyMemory<T>
,而不是旧的ArraySegment<T>
。演示小提琴here。
话虽如此,您也非常接近达到其他内存限制,包括:
byte []
阵列大小,即int.MaxValue
,即2 GB。设置
gcAllowVeryLargeObjects
不会增加此限制。gcAllowVeryLargeObjects
只会增加数组可以容纳的内存量。长度仍限制为int.MaxValue
。此限制在客户端和服务器端都将是一个问题。
StringBuilder
可容纳的最大字符数,从引用源可以看出,它是int.MaxValue
。如果您有大量的客户端同时上传大量数据,即使没有客户端尝试DOS攻击,您的服务器也会再次耗尽内存。
我建议您重新考虑允许任意大的文件上传并将其缓冲在客户端和服务器端的内存中的设计。即使您决定出于业务原因需要支持大于1或2GB的文件上传,我建议你采用一个一种解决方案,其中使用
XmlWriter
和XmlReader
写入和读取内容,而无需将整个内容加载到内存中。要开始,请参阅