关于客户端文件服务器端的 c#:System.OutOfMemoryException

System.OutOfMemoryException on server side for client files

我正在从客户端获取数据并将其保存到本地主机上的本地驱动器。我已经检查了它的 221MB 文件,但对 1Gb 文件的测试给出了以下异常:

An unhandled exception of type 'System.OutOfMemoryException' occurred in mscorlib.dll

以下是服务器端的异常代码。

更新

服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
      public void Thread()
        {
           TcpListener tcpListener = new TcpListener(ipaddr, port);
           tcpListener.Start();
           MessageBox.Show("Listening on port" + port);      
           TcpClient client=new TcpClient();
           int bufferSize = 1024;
           NetworkStream netStream;
           int bytesRead = 0;
           int allBytesRead = 0;

           // Start listening
           tcpListener.Start();

           // Accept client
           client = tcpListener.AcceptTcpClient();
           netStream = client.GetStream();

          // Read length of incoming data to reserver buffer for it
           byte[] length = new byte[4];
           bytesRead = netStream.Read(length, 0, 4);
           int dataLength = BitConverter.ToInt32(length,0);

         // Read the data
           int bytesLeft = dataLength;
           byte[] data = new byte[dataLength];

           while (bytesLeft > 0)
             {

                int nextPacketSize = (bytesLeft > bufferSize) ? bufferSize : bytesLeft;

                bytesRead = netStream.Read(data, allBytesRead, nextPacketSize);
                allBytesRead += bytesRead;
                bytesLeft -= bytesRead;

             }

           // Save  to desktop
           File.WriteAllBytes(@"D:\\LALA\\Miscellaneous" + shortFileName, data);

          // Clean up
          netStream.Close();
          client.Close();

    }

我首先从客户端获取文件大小,然后是数据。

1).我应该增加缓冲区大小或任何其他技术吗?

2)。 File.WriteAllBytes()File.ReadAllBytes() 似乎阻塞并冻结了 PC。是否有任何异步方法可以帮助提供在服务器端接收的文件的进度。


在将其写入磁盘之前,您无需将整个内容读入内存。只需直接从网络流复制到 FileStream:

1
2
3
4
5
6
7
8
9
10
byte[] length = new byte[4];
// TODO: Validate that bytesRead is 4 after this... it's unlikely but *possible*
// that you might not read the whole length in one go.
bytesRead = netStream.Read(length, 0, 4);
int bytesLeft = BitConverter.ToInt32(length,0);

using (var output = File.Create(@"D:\\Javed\\Miscellaneous" + shortFileName))
{
    netStream.CopyTo(output, bytesLeft);
}

请注意,您应该使用 using 语句,而不是显式调用 netStream.Close()

1
2
3
4
using (Stream netStream = ...)
{
    // Read from it
}

这样即使抛出异常,流也会被关闭。


发生"内存不足"异常是因为您试图在将整个文件转储到磁盘之前将其放入内存。这是次优的,因为您不需要内存中的整个文件来写入文件:您可以以合理大小的增量逐块读取它,然后随时写出。

从 .NET 4.0 开始,您可以使用 Stream.CopyTo 方法在几行代码中完成此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Read and ignore the initial four bytes of length from the stream
byte[] ignore = new byte[4];
int bytesRead = 0;
do {
    // This should complete in a single call, but the API requires you
    // to do it in a loop.
    bytesRead += netStream.Read(ignore, bytesRead, 4-bytesRead);
} while (bytesRead != 4);
// Copy the rest of the stream to a file
using (var fs = new FileStream(@"D:\\Javed\\Miscellaneous" + shortFileName, FileMode.Create)) {
    netStream.CopyTo(fs);
}
netStream.Close();

从 .NET 4.5 开始,您也可以使用 CopyToAsync,这将为您提供一种异步读写的方法。

注意从流中删除前四个字节的代码。这样做是为了避免将流的长度与"有效负载"字节一起写入。如果您可以控制网络协议,则可以更改发送方以停止为其长度添加前缀,并删除在接收方读取并忽略它的代码。


CLR 对每个对象的限制略低于 2GB。然而这是理论上的,实际上你可以分配多少内存取决于框架允许你分配多少内存。我不希望它允许您分配 1 GB 数据表。您应该分配较小的表,并将数据分块写入磁盘文件。