簡體   English   中英

TcpClient讀取OutOfMemoryException

[英]TcpClient read OutOfMemoryException

我在斷斷續續的OutOfMemoryException上有問題,

緩沖區=新字節[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。

道格拉斯(Douglas)的答案似乎在馬克(?)上,我將進行測試。 有關信息:服務器(發送程序)程序構建為“任何CPU”,在Windows 7 x64 pc上運行。 雖然客戶端(接收器)以x86構建,並且(在此測試期間)在XP上運行。 但是還必須進行編碼才能在其他Windows x86或x64上運行。

您需要注意架構的字節序 ,特別是因為BitConverter的行為取決於架構。 就目前而言,當您在不同字節序的體系結構之間傳輸數據時,代碼可能會失敗。 例如,假設一條消息的大小為241個字節。 發送者(我們假設是big-endian)將通過發送[0,0,0,241]字節序列來指示此大小。 在大字節序接收器上,這將被正確解釋為241,而在小字節序接收器上,這將被解釋為4,043,309,056(等於241×256 3 )。 如果您嘗試分配那么大的字節數組,則很可能會收到OutOfMemoryException

假設您的傳入流始終是高位優先的,則可以通過調整代碼以在架構為低位優先時反轉數組來處理此問題:

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);

您在談論數據包,但這不是TCP公開的概念。 TCP公開字節流,僅此而已。 不管有多少個Send呼叫。 它可以將一個Send呼叫拆分為多個讀取,並合並多個Send或這些的混合。

Read的返回值告訴您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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM