TcpClient读取OutOfMemoryException

时间:2012-01-21 15:22:14

标签: c# .net out-of-memory tcpclient

我遇到间歇性OutOfMemoryException 的问题,

  

buffer = new byte [metaDataSize];

(在//读取命令的元数据。)

这是否意味着我尝试阅读完整的信息,而只收到部分信息?万一,处理这个问题的可靠方法是什么?顺便说一句,我需要可变长度的消息,因为大多数都非常短,而偶尔的消息非常大。我应该在邮件前面附上完整的邮件大小吗?但是,在尝试从中读取之前,我怎么能知道流包含了多少? (因为看起来读取有时会在尝试读取特定长度时失败,就像我目前所做的那样)

    public static Command Read(NetworkStream ns)
    {
        try
        {
                //Read the command's Type.
                byte[] buffer = new byte[4];
                int readBytes = ns.Read(buffer, 0, 4);
                if (readBytes == 0)
                    return null;
                CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0));

                //Read cmdID
                buffer = new byte[4];
                readBytes = ns.Read(buffer, 0, 4);
                if (readBytes == 0)
                    return null;
                int cmdID = BitConverter.ToInt32(buffer, 0);

                //Read MetaDataType
                buffer = new byte[4];
                readBytes = ns.Read(buffer, 0, 4);
                if (readBytes == 0)
                    return null;
                var metaType = (MetaTypeEnum)(BitConverter.ToInt32(buffer, 0));

                //Read the command's MetaData size.
                buffer = new byte[4];
                readBytes = ns.Read(buffer, 0, 4);
                if (readBytes == 0)
                    return null;
                int metaDataSize = BitConverter.ToInt32(buffer, 0);

                //Read the command's Meta data.
                object cmdMetaData = null;
                if (metaDataSize > 0)
                {
                    buffer = new byte[metaDataSize];

                    int read = 0, offset = 0, toRead = metaDataSize;
                    //While 
                    while (toRead > 0 && (read = ns.Read(buffer, offset, toRead)) > 0)
                    {
                        toRead -= read;
                        offset += read;
                    }
                    if (toRead > 0) throw new EndOfStreamException();

                    // readBytes = ns.Read(buffer, 0, metaDataSize);
                    //if (readBytes == 0)
                    //    return null;
                    // readBytes should be metaDataSize, should we check? 

                    BinaryFormatter bf = new BinaryFormatter();
                    MemoryStream ms = new MemoryStream(buffer);
                    ms.Position = 0;
                    cmdMetaData = bf.Deserialize(ms);
                    ms.Close();
                }
                //Build and return Command
                Command cmd = new Command(cmdType, cmdID, metaType, cmdMetaData);

                return cmd;
        }
        catch (Exception)
        {

            throw;
        }

    }

WRITE方法:

    public static void Write(NetworkStream ns, Command cmd)
    {
        try
        { 

            if (!ns.CanWrite)
                return;

            //Type [4]
            // Type is an enum, of fixed 4 byte length. So we can just write it.
            byte[] buffer = new byte[4];
            buffer = BitConverter.GetBytes((int)cmd.CommandType);
            ns.Write(buffer, 0, 4);
            ns.Flush();

            // Write CmdID, fixed length [4]
            buffer = new byte[4];                    // using same buffer
            buffer = BitConverter.GetBytes(cmd.CmdID);
            ns.Write(buffer, 0, 4);
            ns.Flush();

            //MetaDataType [4]
            buffer = new byte[4];
            buffer = BitConverter.GetBytes((int)cmd.MetaDataType);
            ns.Write(buffer, 0, 4);
            ns.Flush();

            //MetaData (object) [4,len]
            if (cmd.MetaData != null)
            {
                BinaryFormatter bf = new BinaryFormatter();
                MemoryStream ms = new MemoryStream();
                bf.Serialize(ms, cmd.MetaData);

                ms.Seek(0, SeekOrigin.Begin);

                byte[] metaBuffer = ms.ToArray();
                ms.Close();

                buffer = new byte[4];
                buffer = BitConverter.GetBytes(metaBuffer.Length);
                ns.Write(buffer, 0, 4);
                ns.Flush();

                ns.Write(metaBuffer, 0, metaBuffer.Length);
                ns.Flush();

                if (cmd.MetaDataType != MetaTypeEnum.s_Tick)
                    Console.WriteLine(cmd.MetaDataType.ToString() + " Meta: " + metaBuffer.Length);
            }
            else
            {
                //Write 0 length MetaDataSize
                buffer = new byte[4];
                buffer = BitConverter.GetBytes(0);
                ns.Write(buffer, 0, 4);
                ns.Flush();
            }

        }
        catch (Exception)
        {

            throw;
        }
    }

VB.NET:

Private tcp As New TcpClient 
Private messenger As InMessenger    
Private ns As NetworkStream 

Public Sub New(ByVal messenger As InMessenger)
    Me.messenger = messenger
End Sub

Public Sub Connect(ByVal ip As String, ByVal port As Integer)

    Try
        tcp = New TcpClient


        Debug.Print("Connecting to " & ip & " " & port)

        'Connect with a 5sec timeout
        Dim res = tcp.BeginConnect(ip, port, Nothing, Nothing)
        Dim success = res.AsyncWaitHandle.WaitOne(5000, True)

        If Not success Then
            tcp.Close()

        Else
            If tcp.Connected Then
                ns = New NetworkStream(tcp.Client)

                Dim bw As New System.ComponentModel.BackgroundWorker
                AddHandler bw.DoWork, AddressOf DoRead
                bw.RunWorkerAsync()

            End If
        End If


    Catch ex As Exception
        Trac.Exception("Connection Attempt Exception", ex.ToString)
        CloseConnection()
    End Try
End Sub


Private Sub DoRead()

    Try
        While Me.tcp.Connected

            ' read continuously : 
            Dim cmd = CommandCoder.Read(ns)

            If cmd IsNot Nothing Then
                HandleCommand(cmd)
            Else
                Trac.TraceError("Socket.DoRead", "cmd is Nothing")
                CloseConnection()
                Exit While
            End If

            If tcp.Client Is Nothing Then
                Trac.TraceError("Socket.DoRead", "tcp.client = nothing")
                Exit While
            End If
        End While
    Catch ex As Exception
        Trac.Exception("Socket.DoRead Exception", ex.ToString())
        CloseConnection()
        EventBus.RaiseErrorDisconnect()
    End Try

End Sub

修改

我输入了一些WriteLine,发现发送的一些包在接收端识别出错误的大小。因此对于某个消息应该是9544的metaDataSize被读取为5439488,或类似的不正确的值。我假设在几个世界中这个数字太大而导致OutOfMemoryException。

似乎道格拉斯的回答可能是标记(?),我会测试。有关信息:服务器(发件人)程序构建为“任何CPU”,在Windows 7 x64 pc上运行。虽然客户端(接收器)构建为x86,并且(在此测试期间)在XP上运行。但也必须编码以适用于其他Windows x86或x64。

2 个答案:

答案 0 :(得分:2)

您需要注意架构的endianness,特别是因为BitConverter的行为取决于架构。就目前而言,当您在不同字节序的体系结构之间传输数据时,您的代码可能会失败。例如,想象一下,大小为241字节的消息。发送者 - 我们将假设为大端 - 将通过发送[0,0,0,241]的字节序列来指示此大小。这将在大端接收器上正确解释为241,但在小端接收器上为4,043,309,056(等于241×256 3 )。如果您尝试分配大的字节数组,则很可能会得到OutOfMemoryException

假设您的传入流始终是big-endian,那么当您的体系结构是little-endian时,通过调整代码来反转数组来处理这个问题:

buffer = new byte[4];
readBytes = ns.Read(buffer, 0, 4);
if (readBytes == 0)
    return null;
if (BitConverter.IsLittleEndian)
    Array.Reverse(buffer);

修改:回复评论:

每当您要使用BitConverter.ToInt32方法将4字节序列转换为整数时,您需要更正字节序。使用BinaryFormatter时,您无需更正字节顺序,因为它可以透明地处理字节顺序。

我认为字节序取决于你机器的物理架构,但我从未研究过具体细节。为了安全起见,无论您是在x86还是x64上运行,都不应该假定特定的字节序。

如果您还负责服务器代码,还需要更正那里的字节顺序。例如,要从服务器发送cmdID值:

int cmdID = 22;  // for the example

byte[] buffer = BitConverter.GetBytes(cmdID);
if (BitConverter.IsLittleEndian)
    Array.Reverse(buffer);
ns.Write(buffer, 0, 4);

答案 1 :(得分:2)

你在谈论数据包,但这不是TCP暴露的概念。 TCP暴露了一个字节流,仅此而已。它并不关心有多少Send次呼叫。它可以将一个Send调用分成多个读取,并合并多个发送,或者这些发送的混合。

Read的返回值告诉您读取了多少字节。如果此值大于0,但小于传递给Read的长度,则传递的字节数减少。您的代码假定已读取0length个字节。这是一个无效的假设。

您的代码也会遇到端序问题,但我认为您的系统都是小端,因此这不太可能导致您当前的问题。


如果您不关心阻止(您的现有代码已经在循环中阻止,那么这不是其他问题)您可以在流上使用BinaryReader

它有像ReadInt32这样的辅助方法,可以自动处理部分读取,并使用固定字节序(总是很少)。

buffer = new byte[4];
readBytes = ns.Read(buffer, 0, 4);
if (readBytes == 0)
    return null;
int cmdID = BitConverter.ToInt32(buffer, 0);

变为:

int cmdId = reader.ReadInt32();

如果意外地遇到流的末尾,它将抛出EndOfStreamException,而不是返回null