none
TcpClient / Bildschirmübertragung / Packets zu gross RRS feed

  • Frage

  • Hallo liebes msdn Forum,

    Ich bin dabei einen Bildschirmübertragungs-client & Server zu programmieren. Die Verbindung und das authentifizieren funktionieren. Wenn ich allerdings Versuche das Bild als einzelnes Packet zu veschicken kommt es zu einer OutOfMemoryException. 

    Hier der Code zum verschicken des Bildes:

    // In der RemoteDesktop-Klasse
    private void Capture()
    {
        Bitmap bmp = CaptureScreen.CaptureDesktopWithCursor();
        bmp = new Bitmap(bmp, new Size(960, 540));
        client.Protocol.SendScreen(bmp);
    }
    
    // In der Protocol-Klasse
    public void SendScreen(Bitmap bmp)
    {
        MemoryStream memoryStream = new MemoryStream();
        bmp.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
        bmp.Dispose();
        byte[] package = memoryStream.ToArray();
        Send(8, package); // Packet-ID = 8
    }
    
    private bool Send(Int16 packet, byte[] content)
    {
        return SendBytes(BitConverter.GetBytes(packet), content);
    }
    
    private bool SendBytes(byte[] packetNo, byte[] send)
    {
    try
        {
            // Get Packet Length
            int bytes = send.Length + 6;
            byte[] length = BitConverter.GetBytes(bytes);
            // Write Packet
            netStream.Write(length, 0, 4);
            netStream.Write(packetNo, 0, 2);
            netStream.Write(send, 0, send.Length);
            netStream.Flush();
            return true;
         }
         catch
         {
             return false;
         }
    }

    Und das Gegenstück zum Empfangen

    private void Read(IAsyncResult ar)
    {
        try
        {
            // get buffer size
            int buffer = tcp.ReceiveBufferSize;
            if (buffer >= 6)
            {
                byte[] size = new byte[4];
                netStream.Read(size, 0, 4);
                int packLen = BitConverter.ToInt32(size, 0) - 4;
                // create buffer array
                byte[] read = new byte[packLen];
                // read stream to buffer
                netStream.Read(read, 0, packLen);
                // handle 
                ReceivedEvent(this, read);
            }
            // start async read
            tcp.GetStream().BeginRead(new byte[] { 0 }, 0, 0, Read, null);
        }
        catch (Exception)
        {
                DisconnectedEvent(this, "Fehler beim lesen des Streams");
        }
    }
    
    // Daraufhin wird überprüft ob der Client verifiziert ist, und das Packet gehandlet.
    
    public bool Handle(byte[] recv)
    {
        // Create Packet number / header
        byte[] pack = new byte[] { recv[0], recv[1] };
        Int16 recvHeader = BitConverter.ToInt16(pack, 0);
        // Create Packet Content
        int bytesToEliminate = 2;
        int newLength = recv.Length - bytesToEliminate;
        byte[] recvContent = new byte[newLength];
        Array.Copy(recv, bytesToEliminate, recvContent, 0, newLength);
        string message = "";
        if (!skipMessageBuilding.Contains(recvHeader))
            message = ByteHelper.GetString(recvContent);
    
        switch (recvHeader)
        {
            case 8:
                NewScreenEvent(recvContent);
                break;
        }
    }

    Da Ich noch nicht sehr viel mit Netzwerkprogrammierung gearbeitet habe wäre ich für jeden Tipp dankbar ^^

    Hauptsächlich würde es darum gehen, wie ich die OutOfMemory-Exception verhindern kann bzw ob es sinn Macht die Packete zu teilen und wenn ja wie das am einfachsten geht. MfG. Basic

    Freitag, 10. Oktober 2014 22:47

Antworten

  • Hallo,

    die OutMemoryException dürfte vor allem daraus resultieren, dass Du großzügig mit Byte-Arrays umgehst. Vor allem unter 32-Bit wird dabei schnell der Speicher eng, zumal große Arrays (> 85KB) im sog. Large Object Heap (LOH)[1] verwaltet werden und was zur Fragmentierung führt und längerfristig wiederum eine OOM Ausnahme verursachen kann.

    Bei einer IP Netzwerkübertragung werden die Daten ohnehin in kleinere Blöcke aufgeteilt (siehe u. a. MTU),  weswegen man größere Blöcke in mehrere Schreibvorgänge aufteilen sollte. Der TcpClient verwendet z. B. 8192 Bytes (8KB) im Standard dafür dafür. Wenn man das berücksichtigt, kann man den Speicherbedarf deutlich verringern.

    Wenn Du Paketweise überträgst, kannst du den MemoryStream direkt verwenden, in dem Du in an den Anfang stellst und via Stream.CopyTo überträgst - alternativ schreibt MemoryStream.WriteTo den kompletten Inhalt. Sollte es trotz allem zu eng mit dem Speicher werden, verwende eine temporäre Datei anstatt des MemoryStreams.

    Auf der Empfangsseite (Read) kommen die Pakete ggf. wiederum geteilt an - es ist nicht garantiert, dass bis zum Maximum gelesen wird, vielmehr sollte man immer die Rückgabe (= effektive Länge) von Read auswerten und weiter verwenden.

    In den Beispielen zu C# in a Nutshell findest Du einige Implementierungen, die Du für asynchrones Senden und Empfangen als Basis nehmen kannst.

    Gruß Elmar

    [1] http://blogs.msdn.com/b/dotnet/archive/2011/10/04/large-object-heap-improvements-in-net-4-5.aspx

    Samstag, 11. Oktober 2014 08:27
    Beantworter