我正在使用一个程序,该程序使用TCP
通过网络发送/接收文件。该程序发送多个文件,因此在用户退出该程序之前,流不会关闭。
我面临的问题是,当我发送一个700mb的文件时,我的服务器程序专用内存会增长到700000K,严重削弱了我的计算机性能。当试图发送另一个700mb的文件时,服务器抛出一个System.OutOfMemoryException
。
有人能告诉我我做错了什么吗?
服务器端代码:
using ( FileStream fs = new FileStream("dracula.avi", FileMode.Open, FileAccess.Read))
{
byte[] data = new byte[fs.Length];
int remaining = data.Length;
int offset = 0;
strWriter.WriteLine("Content-Length: " + data.Length);
strWriter.Flush();
Thread.Sleep(1000);
while (remaining > 0)
{
Thread.Sleep(10);
int read = fs.Read(data, offset, remaining);
remaining -= read;
offset += read;
}
fs.Flush();
fs.Close();
}
strm.Write(data, 0, data.Length);
strm.Flush();
GC.Collect();
您当前正在将整个文件读取到内存中,即使您只想将其复制到另一个流中。不要那样做。只需一次迭代一个区块:读取区块、写入区块、读取区块、编写区块等等。如果你正在使用。NET 4,您可以使用Stream.CopyTo
来实现此目的。
您缓冲的是读取,而不是写入。这个程序正是按照你的要求去做的——分配一个巨大的区块或内存,并在发送一个字节之前将其全部填满。
更好的方法是从文件中读取一个小块(为了便于论证,4096字节),然后将该块写入输出流。通过这样做,每个连接将只使用4096个字节,这更具可扩展性。
OOM情况通常发生在系统内存不足或在32位进程中地址空间(2000MB)不足的情况下。
你说它可以成功复制一个,但不能复制两个?这两个是同时发生的还是连续发生的?你的线程模型是什么?另外,这个例子是一个片段,你似乎有一个StreamWriter和一个Stream用于写作,这些对象会消失吗?
小心GC。收集Microsoft不建议显式调用,因为如果您不正确使用它,可能会导致对象的生存时间超过所需时间。这是因为当你做GC时。收集,您正在将对象提升到更高一代。根据我的经验,最好确保您正在释放对象,并让框架决定GC的内容/时间。收集
我会熟悉WinDBG+SOS,这允许您查看堆上的对象。
试试这个:
- 启动WinDBG并连接到您的进程
- 如果使用4.0,请键入".loadby sos clr",否则键入".loadsby sos mscorwks">
- 按F5继续
- 复制一个文件,等待它完成
- 按CTRL+BREAK
- 键入"!dumpeap-stat",查看结果,查找应该消失的对象
- 对于每个应该消失的对象,获取MT值
- 键入"!dumpeap-mt{0}",将{0}替换为上面步骤中的值
- 这是一个实例列表,抓取其中一个对象地址
- 键入"!gcroot{0}",将{0}替换为对象地址
这应该会告诉你什么是对象的根,然后你需要找出如何展开,例如不需要的null对象。
最好在读取数据块后立即发送。我没有测试代码,但它应该类似于;
var bufferLenght = 1024;
byte[] buffer;
while (remaining > 0)
{
buffer = new byte[1024];
int len = fs.Read(buffer, offset, bufferLenght);
remaining -= len;
offset += len;
strm.Write(buffer, 0, len);
}