vb.net - Client/Server sauber beenden? SerializationException

  • VB.NET

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    vb.net - Client/Server sauber beenden? SerializationException

    Hallo an euch alle :)

    Bin schon seit 2009 hier im Forum regelmäßig am schauen und hatte auch einen Account, allerdings keine Daten mehr dafür :) Also ein neuer Account. Ich habe vor 15 Jahren Anwendungsentwicklung gelernt aber leider nach der Ausbildung bis heute nie wirklich am programmieren gewesen trotz Arbeit in der IT-Branche. Deswegen immer mal zuhause um nicht alles zu verlernen :D

    Nachdem ich ein wenig mit Datenbanken rumprobiert habe bin ich nun beim thema Networking angekommen. Einfache Client/Server anwendung und Daten hin und her schicken. Klappt auch alles soweit ganz gut. Allerdings wirft sich für mich immer wieder die Frage auf; Wie beendet man am saubersten eine Verbindung (Wer sollte diese am besten schließen) um danach eine neue Verbindung aufzubauen?

    Zum Test habe ich eine kleine Client/Server Anwendung die Dauerhaft das Desktopbild zum Server senden und dieser es gleich in eine einfache pictureBox anzeigt. Funktioniert auch alles super.

    Clientseite:
    Hier läuft er in der While Schleife solange durch bis ich manuell den "Verbunden" Boolean auf False setze. Dann soll er stream und client schließen. Funktioniert auch soweit super.

    VB.NET-Quellcode

    1. Public Sub Beginn()
    2. Try
    3. Dim client As New TcpClient
    4. Dim stream As NetworkStream
    5. client.Connect("127.0.0.1", 1909)
    6. While Verbunden = True
    7. Dim bf As New BinaryFormatter
    8. stream = client.GetStream
    9. bf.Serialize(stream, Desktop())
    10. End While
    11. client.GetStream.Close()
    12. client.Close()
    13. stream.Close()
    14. Catch ex As Exception
    15. MsgBox(ex.Message)
    16. End Try
    17. End Sub
    18. Public Function Desktop() As Image
    19. Dim bounds As Rectangle = Nothing
    20. Dim screenshot As System.Drawing.Bitmap = Nothing
    21. Dim graph As Graphics = Nothing
    22. bounds = Screen.PrimaryScreen.Bounds
    23. screenshot = New Bitmap(bounds.Width, bounds.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb)
    24. graph = Graphics.FromImage(screenshot)
    25. graph.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy)
    26. Return screenshot
    27. End Function


    Serverseite:
    Auch auf Serverseite arbeite ich mit einem Bool um die Sub zu verlassen und somit auch (das hoffe ich) des Thread beende. Die Listen-Sub starte ich in einem Thread und das ".Abort" beendet ja nicht wirklich den thread. Beende ich jetzt den Client, bekomme ich bei der picturebox den Fehler wie unten im Kommentar. Ich hatte zwar mal ein wenig mit invoke probiert, aber das funktionierte auch nicht. Ganz unabhängig von dem Fehler. Was wäre die Beste Lösung um die Verbindung zu schließen um danach wieder sauber aufzubauen?

    VB.NET-Quellcode

    1. Private Sub Listen()
    2. If Verbunden = False Then
    3. Exit Sub
    4. End If
    5. Dim client As New TcpClient
    6. Dim Server As New TcpListener(IPAddress.Any, 1909)
    7. Dim stream As NetworkStream
    8. While client.Connected = False
    9. server.Start()
    10. client = server.AcceptTcpClient
    11. End While
    12. Dim bf As New BinaryFormatter
    13. While client.Connected = True
    14. stream = client.GetStream
    15. pbDesktop.Image = bf.Deserialize(stream) 'Hier entsteht der Fehler: System.Runtime.Serialization.SerializationException: "Das Datenstromende wurde erreicht, bevor die Verarbeitung abgeschlossen wurde."
    16. End While
    17. client.Close()
    18. stream.Close()
    19. Server.Stop()
    20. End Sub
    21. Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
    22. Verbunden = True
    23. Listenthread.Start() 'Die Listen-Sub wird als Thread gestartet
    24. btnStart.Enabled = False
    25. End Sub


    Wenn das "ein wenig" dreckig programmiert aussieht, seht es mir nach. Ich muss langsam erstmal wieder rein kommen :D
    edit: ich habe bewusst try catch weggelassen um die Fehler zu sehen und das ganze ein wenig besser zu verstehen ;)
    edit edit: Euch allen einen schönen 4. Advent :)

    Beste Grüße

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Litmann“ ()

    @Litmann Willkommen im Forum.
    Der Server sollte über die komplette Programmlaufzeit aktiv sein.
    Ein Client kann ebenfalls über die komplette Programmlaufzeit aktiv sein, muss aber nicht.
    Ein Client kann auch pro Verbindung / Datenaustausch aktiv sein und danach wieder beendet werden.
    Welche der beiden Varianten Du wählst, hängt davon ab, was die beiden miteinander zu tuscheln haben.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Danke für deine Antwort RodFromGermany. Gerade beim Client hatte ich gedacht das dieser am besten jedes mal neu erstellt werden sollte um nach einem Verbindugsverlust nicht iwo noch was "offen" ist und eine neue Verbindung verhindert?! Ich weiß nicht ob ich da einen Denkfehler habe, aber so hatte ich es im Kopf.

    Auch hatte ich im Kopf das es Sinnvoll ist das der Client dem Server "sagt" ich gehe offline und dann die Verbindung sauber getrennt wird und die nötigen TcpListener und TcpClient wieder freigegeben werden.
    Willkommen (zurück) im Forum.
    Zu Unsauberkeiten:

    Litmann schrieb:

    ich habe bewusst try catch weggelassen
    Aber leider nicht beim Client. Fange nur Exceptions ab, die Du kennst und sinnvoll bearbeiten kannst.
    Im Server: Dim client As New TcpClient: Das New muss weg. Du willst keinen neuen Client erstellen, sondern erhältst ihn über den Stream.
    Vergleiche mit True/False sind redundant. If Verbunden = False Then -> If Not Verbunden Then, While client.Connected = True -> While client.Connected
    Client, Stream und Server implementieren (m.E. alle) IDisposable. Nutze daher Using-Blöcke, wenn es lokale Variablen sind oder nutze die .Dispose-Funktion, um das Objekt nach Close sauber aufzuräumen.
    Ohne den Code probiert zu haben: Warum erstellst Du beim Client immer wieder einen neuen BinaryFormatter: Dim bf As New BinaryFormatter? Zieh ihn aus der While-Schleife raus. Einer sollte reichen. Und warum startest Du im Server-Code den Server immer wieder neu (Zeile#11-#14). Einmal reicht doch -> aus der While-Loop raus damit. Die ganzen While-Loop enthalten einzelne Aufgabe, die m.E. nur einmal ausgeführt werden müssen/sollten.

    btw: Ist das der Anfang eines RemoteControls?
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Tatsache, beim Client hatte ich sie noch drinnen :D
    Okay das mit dem Client macht Sinn das der nicht jedesmal neu erstellt wird und jap; Den BinaryFormatter jedesmal neu zu erstellen macht keinen Sinn.

    Mit "using" muss ich mich mal ein wenig einlesen wie man das am besten nutzt.

    Zu .Dispose eine kurze Frage. Wenn durch ein Objekt viel Arbeitsspeicher genutzt wird bzw vollpumpt; Gibt .Dispose beim "aufräumen" auch diesen wieder frei?
    Das ist die Aufgabe von Dispose. Sauber machen, wo der GarbageCollector (GC) nicht helfen kann. Standardmäßig kümmert der sich zwar um die managed resources. Aber wenn da eben z.B. für ein Objekt Dateizugriffe gebraucht werden und Dateien offen sind, hat der GC nicht die Möglichkeit, das Objekt zu entsorgen, da der GC ja nicht weiß, ob die Dateistreams wirklich geschlossen werden dürfen. Dafür gibt es dann Dispose. Achtung: Dispose ist eine Funktion einer (nacheditiert) Schnittstelle (Interface), also eine Aufgabe, die ein Objekt haben muss, welches dieses Interface implementiert. Aber wie es das macht, darum muss sich die Objektklasse selber kümmern! Für den Fall, dass Du selber mal mit Klassen arbeitest, die mit IDisposable implementieren sollen.
    Die Klasse kümmert sich also in ihrer Dispose-Prozedur um das Aufräumen von unmanaged resources, schließt dann also z.B. Dateistreams, sodass der GC seine Arbeit machen kann.
    Ein Using-Block erspart Dir (bei lokalen Variablen) den expliziten Aufruf von .Dispose:

    VB.NET-Quellcode

    1. Using DeineVariable As New DeineKlasseDieIDisposableImplementiert
    2. 'Code, der DeineVariable nutzt
    3. End Using

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VaporiZed“ ()

    ahhh okay, wieder was gelernt :) Ich danke dir VaporiZed. Zum Thema using lese ich mich gerad ein wenig ein wie man das am besten nutzt.

    Wenn ich jetzt wie in dem Beispiel einen stream habe, der jetzt zum Beispiel vom Client beendet wird habe ich ja auf der Serverseite ein unerwartetes Datenstromende wo er einen Fehler schmeißt. Sollte bzw kann man an genau dieser Stelle überprüfen: "Okay der Stream wurde unerwartet beendet; schließe diesen stream jetzt egal ob noch nicht alle Daten gelesen wurden"?

    Zur Frage: btw: Ist das der Anfang eines RemoteControls?
    Nein. Definitiv nicht. ich habe urlaub und möchte einfach mal wieder rein kommen. hatte vorher mal ein wenig probiert mit text hin und her schicken und übers googeln bin ich bei codeproject mal auf so nen Remote Desktop Viewer gestoßen. Mich hats gereizt mal selbst zu probieren ein "Live" Bild hin und her zu schicken als "nur eine Datei".. Ich versuche das Thema Networkstream und Client/Server Verbindung ein wenig zu verstehen.

    Litmann schrieb:

    schließe diesen stream jetzt egal ob noch nicht alle Daten gelesen wurden
    Er wurde doch grade beendet.
    Der Server muss nur darauf reagieren, indem er diesen Client "vergisst".
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ja, dafür kannst Du ein gezieltes (!) Try-Catch einsetzen. Kommt alternativ eigentlich statt der Streamauslesung ein ReadLine infrage? Denn da gibt's ja feste Blöcke. EIne Zeile wird geschrieben, gelesen. Und Am Ende wird auf die nächste Zeile gewartet. Und dann kööntest Du auch vom Client eine Zeile schicken: "Halle Server, ich bin dann mal weg." Und der Server kann exceptionfrei seine Verbindung zu Client kappen.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    RodFromGermany schrieb:

    Litmann schrieb:

    schließe diesen stream jetzt egal ob noch nicht alle Daten gelesen wurden
    Er wurde doch grade beendet.
    Der Server muss nur darauf reagieren, indem er diesen Client "vergisst".


    ja schon. Aber doch ziemlich "hart" und gibt mir das ja mit einem Fehler auch klar zu verstehen. Ich könnte darum ein Try Catch setzen und im catch block könnte ich dann den client schließen lassen. Aber das wäre glaube ich nicht so sauber. Oder ich habe gerad einfach einen Denkfehler.

    VaporiZed: Zu deinem Beitrag; uff müsste ich für solche Strings nicht noch eine Verbindung oder einen stream offen halten um dem Server das mitzuteilen? Soweit wollte ich das kleine Projekt eigentlich nicht ausweiten, vor allem weil ich da noch nicht so fit drin bin :)
    Ah, Kommando zurück. Du brauchst ja nen Stream für den BinaryFormatter. Hat sich damit erledigt.

    ##########

    @Litmann: Zwischenfrage: Geht das gesamte Konstrukt überhaupt? Erhältst Du Bilder, während der Client verbunden ist? Oder crasht das ganz schon zu Beginn?

    Zurück zum Beenden: Da der Stream wohl (jetzt erstmal) immer nur Bilder enthält, ist der gezielte Try-Catch-Block schon eine Möglichkeit. Oder Du gibst den Stream nicht direkt an den BinaryFormatter, sondern merkst Dir die Streamposition, kopierst den Streaminhalt in einen 2. Stream, der an einen StreamReader gekoppelt ist, nutzt ReadToEnd, um an den aktuellen Inhalt ranzukommen, und schaust, ob da ggf. eine Abbruchnachricht vom Client drinnen ist. Die schickt der Client, wenn er sich verabschieden will. Dann weiß der Server bescheid und kann die Verbindung sauber beenden. Wenn diese spezielle Nachrich nicht drin ist, setz die Streamposition zurück und führe mit Deinem BinaryFormatter das Deserialisieren aus.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „VaporiZed“ ()

    Doch doch es funktioniert sogar super. Habe das Bild und alles funktioniert, bis ich halt zb. den Client schließe, dann kommt der fehler mit dem Datenstrom.

    hmm aber selbst wenn ich das jetzt so machen würde das der client einen string rüberschickt um zu sagen "hör mal ich verabschiede mich jetzt" .. angenommen das ClientProgramm stürtzt ab und beendet die Verbindung müsste man ja die Möglichkeit haben den server "relativ sauber" zu beenden bzw die verbindung zu schließen oder nicht? Wie gesagt, mit Befehlen hin und her senden zum beenden der verbindung wollte ich jetzt eigentlich nicht arbeiten. :)

    Litmann schrieb:

    bis ich halt zb. den Client schließe
    Du kannst natürlich vom Client an den Server ein Kommando "Exit" senden und dann schließt der Server den Client ohne Exception.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!