c# - TcpClient 读取 OutOfMemoryException

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

我遇到了 间歇性 OutOfMemoryException 问题,在线

buffer = new byte[metaDataSize];

(在//Read the command's Meta data.)

这是否意味着我尝试阅读完整的消息,但只收到了消息的一部分?以防万一,什么是可靠的处理方法?顺便说一句,我需要可变长度的消息,因为大多数消息都很短,而偶尔的消息非常大。我应该在消息前面附上完整的消息大小吗?不过,在尝试读取流之前,我如何知道流包含多少内容? (因为在尝试读取特定长度时,读取有时会失败,就像我现在所做的那样)

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

    }

写入方法:

    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,发现一些发送的包在接收方被识别为错误的大小。因此,某条消息的 metaDataSize 应为 9544,却被读取为 5439488 或类似的错误值。我假设在少数情况下这个数字太大以至于导致 OutOfMemoryException。

看来 Douglas 的回答可能是正确的(?),我会测试。有关信息:服务器(发送方)程序构建为“任何 CPU”,在 Windows 7 x64 pc 上运行。虽然客户端(接收器)构建为 x86,并且(在此测试期间)在 XP 上运行。但也必须编码才能在其他 Windows x86 或 x64 上工作。

最佳答案

你说的是数据包,但这不是 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

关于c# - TcpClient 读取 OutOfMemoryException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8954115/

相关文章:

c# - InvalidDataException : Multipart body length limit 16384 exceeded

c# - Backgroundworker.CancelAsync() 不工作

c# - 从 C# 中的变量执行函数

.net - 从 .NET 与 SQLite 对话的最佳/最简单方法是什么?

java.lang.outofmemoryerror android.graphics.BitmapFactory.nativeDecodeAsset(本地方法)

c# - 加密值每次都不会返回相同的值

.net - F#:函数中有更多返回点,如何处理?

.net - 异步与同步套接字

python - 合并多个 CSV 文件会导致内核死亡

Scala,戊二烯: Is there a straightforward way to replace a stream with an iteration?