none
Streamsocket: wissen, wann Daten empfangen wurden.

    Frage

  • Hallo, 

    ich arbeite momentan mit Hilfe der Streamsocket Klasse an einem Chatclient. Leider weiß ich nun nicht wie das Programm merken könnte, dass der Server etwas an den Client gesendet hat(z.B. eine Nachricht eines anderen Clients). Gibt es dafür eine andere Klasse, oder ein Event, welches genutzt werden könnte?

    Vielen Dank für alle Antworten,

    Mattis Brooker

    Freitag, 12. Januar 2018 12:18

Antworten

  • Hi Mattis,
    nachfolgend eine kleine Demo für eine Klasse, mit der parallel geschrieben und gelesen werden kann, wenn das der >Server unterstützt.

    Imports System.Threading
    Imports Windows.Networking
    Imports Windows.Networking.Sockets
    
    Friend Class ClientTCP
    
      Friend Event Zustand As EventHandler(Of ClientEventArgs)
    
      Private sc As SynchronizationContext = SynchronizationContext.Current
    
      Private host As New HostName("192.168.0.19")
      Private port As String = "8888"
      Private socket As New StreamSocket
    
      Friend Async Sub Connect()
        Try
          Await socket.ConnectAsync(host, port)
          RaiseEvent Zustand(Me, New ClientEventArgs With {.Nachricht = "Verbindung aufgebaut."})
          Task.Run(AddressOf Receive)
        Catch ex As Exception
          RaiseEvent Zustand(Me, New ClientEventArgs With {.Nachricht = ex.ToString})
        End Try
      End Sub
    
      Friend Async Sub Send(txt As String)
        Try
          Dim out = socket.OutputStream.AsStreamForWrite
          Dim wrt As New StreamWriter(out)
          Await wrt.WriteLineAsync(txt & vbCrLf)
          Await wrt.FlushAsync
        Catch ex As Exception
          RaiseEvent Zustand(Me, New ClientEventArgs With {.Nachricht = ex.ToString})
        End Try
      End Sub
    
      Private Async Sub Receive()
        Do
          Dim inp = socket.InputStream.AsStreamForRead()
          Dim rdr As New StreamReader(inp)
          Dim resp = Await rdr.ReadLineAsync()
          sc.Post(New SendOrPostCallback(Sub()
                                           RaiseEvent Zustand(Me, New ClientEventArgs With {.Nachricht = resp})
                                         End Sub), Nothing)
        Loop
      End Sub
    
    End Class


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Montag, 15. Januar 2018 18:47

Alle Antworten

  • Hi Mattis,
    ein Server kann von sich aus nichts an einen Client senden. Der Initiator ist IMMER der Client. Der Server kann lediglich nach der Verbindungsaufnahme durch den Client etwas (zurück)senden. Das bedeutet, dass der Client nach der Verbindungsaufnahme auf Daten wartet und, falls nichts vom Server gesendet wird, ein time-out ausgelöst wird.

    Alternativ kann natürlich jeder Client auch eine Server-Funktionalität bereitstellen, d.h. beide Rechner können sowohl Server als auch Client sein.

    Zuerst solltest Du das logische Sitzungsprotokoll beschreiben, bevor Du mit einer Implementierung beginnst.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 12. Januar 2018 12:56
  • Darum geht es gar nicht. Ich habe eine bestehende Tcp Verbindung mit dem Server sobald ich mein Programm starte. Senden von Daten ist also kein Problem, empfangen theoretisch auch möglich, aber nur wenn ich z.B. einen Button mit click-event erstelle. Das Problem ist also nicht das empfangen an sich, sondern das wissen, wann etwas ausgelesen werden kann. Normalerweise sollte ein Chatclient ja automatisch neue Nachrichten anzeigen und nicht dass man erst irgendeinen Button drücken muss.
    Freitag, 12. Januar 2018 15:54
  • Hi Mattis,
    dann beschreibe mal Deine logische Sitzungsschicht.

    Wenn Du mit dem Button_Click das Senden startest, dann kannst Du auf eine Antwort warten.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks


    Freitag, 12. Januar 2018 16:24
  • Hallo, 

    Ich glaube so weit bin ich noch nicht, zumindest weiß ich nicht was mit einer logischen Sitzungsschicht gemeint ist.

    Eine Antwort nach dem senden ist natürlich nicht unpraktisch, allerdings möchte ich ja auch Daten von anderen Clients über den Server empfangen. Somit sendet der eine Client zwar nichts, sollte aber etwas empfangen.

    Mattis Brooker


    Freitag, 12. Januar 2018 16:36
  • Hi Mattis,
    in der Beschreibung der Sitzungsschicht wird dargelegt, was zu unterschiedlichen Zeitpunkten zu passieren hat. Client schickt eine Anforderung, Server antwortet. Möglich ist auch eine andere Betriebsart. Es wird eine Verbindung aufgebaut und der Networkstream lässt die Übertragung in beide Richtungen zu (Writeable, Readable). Ereignisse können damit nicht direkt ausgelöst werden. Aus dem Networkstream werden Bytes gelesen und ausgewertet. Wenn da eine bestimmte Bytefolge ankommt, kann diese als Ereignis gewertet werden. Das ist in der Sitzungsschicht zu beschreiben, um damit entsprechend das Programm zu entwickeln.

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 12. Januar 2018 18:22
  • Ok, das zweite wäre ja das was ich brauche, wie kann ich denn die Sitzungsschicht einstellen? Und wenn ich das dann hätte, wie könnte ich das Event erstellen, das ausgelöst wird, wenn Daten ankommen?
    Samstag, 13. Januar 2018 17:51
  • Hi Mattis,
    wenn Du den Datentransfer in eine Klasse kapselst und festgelegt hast, wann ein Ereignis auszulösen ist, dann kann dieses Ereignis ausgelöst werden (in VB.NET mit RaiseEvent). Das Hauptprogramm braucht dann nur eine Instanz vom Typ dieser Klasse zu erzeugen und kann das Ereignis abonnieren.

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Samstag, 13. Januar 2018 18:22
  • Das habe ich mir schon gedacht, nur ist mir nicht klar, wann ein Ereignis auszulösen ist bzw. was ich dem Ereignis zuordnen sollte. Es gibt leider nichts wie socket->messageReceived() oder sowas ähnliches. Zumindest bei dem Streamsocket nicht. 
    Wenn es sowas gibt dann habe ich es trotz langer suche nicht gefunden.

    Sonntag, 14. Januar 2018 15:02
  • Hi Mattis,
    wenn die Details der Sitzungsschicht festgelegt sind, dann ist auch klar, was das Ende einer Nachricht ist, z.B. Bytefolge ENDE oder EOF oder der schließende Tag des Root-Elementes eines XML-Streams. 

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Sonntag, 14. Januar 2018 16:46
  • Ich glaube ich hänge da wohl ein wenig hinterher. Vielleicht sollte ich klarstellen, dass ich eigentlich nicht viel mit Networking am Hut habe, und nur jetzt eben mal sowas machen wollte. Denn ein Ende einer Nachricht sagt mir ja nicht, dass eine Nachricht angekommen ist. In Standard C++  würde ich einfach in einem while Loop eine receive Funktion ausführen, und dann falls etwas empfangen wurde, könnte ich damit arbeiten. Aber in UWP kann ich ja kein while Loop erstellen und so stehe ich eben vor diesem Problem. 
    Sonntag, 14. Januar 2018 16:56
  • Hallo,

    auch in UWP kann man das machen, man muss es nur in einen weiteren Thread auslagern damit der UI Thread nicht betroffen ist. In C# würde man das mit Task.Run machen.

    Nochmal als Ergänzung:

    Du musst das Rad natürlich nicht neu erfinden und könntest dir anhang bestehender Chat-Protokolle das eine oder andere abschauen. Das IRC Protokoll ist gut dokumentiert und man findet auch für C++ beispiele für einen Client und Server. Einfach mal mit der Suchmaschine deiner Wahl suchen   


    Gruß Thomas
    Sage nie, ich kann es nicht - sage nur, ich kann es noch nicht!
    Dev Apps von mir: Icon für UWP,  UI Strings
    Andere Dev Apps: UWP Community Toolkit Sample App


    Montag, 15. Januar 2018 06:45
  • Hi Mattis,
    nachfolgend eine kleine Demo für eine Klasse, mit der parallel geschrieben und gelesen werden kann, wenn das der >Server unterstützt.

    Imports System.Threading
    Imports Windows.Networking
    Imports Windows.Networking.Sockets
    
    Friend Class ClientTCP
    
      Friend Event Zustand As EventHandler(Of ClientEventArgs)
    
      Private sc As SynchronizationContext = SynchronizationContext.Current
    
      Private host As New HostName("192.168.0.19")
      Private port As String = "8888"
      Private socket As New StreamSocket
    
      Friend Async Sub Connect()
        Try
          Await socket.ConnectAsync(host, port)
          RaiseEvent Zustand(Me, New ClientEventArgs With {.Nachricht = "Verbindung aufgebaut."})
          Task.Run(AddressOf Receive)
        Catch ex As Exception
          RaiseEvent Zustand(Me, New ClientEventArgs With {.Nachricht = ex.ToString})
        End Try
      End Sub
    
      Friend Async Sub Send(txt As String)
        Try
          Dim out = socket.OutputStream.AsStreamForWrite
          Dim wrt As New StreamWriter(out)
          Await wrt.WriteLineAsync(txt & vbCrLf)
          Await wrt.FlushAsync
        Catch ex As Exception
          RaiseEvent Zustand(Me, New ClientEventArgs With {.Nachricht = ex.ToString})
        End Try
      End Sub
    
      Private Async Sub Receive()
        Do
          Dim inp = socket.InputStream.AsStreamForRead()
          Dim rdr As New StreamReader(inp)
          Dim resp = Await rdr.ReadLineAsync()
          sc.Post(New SendOrPostCallback(Sub()
                                           RaiseEvent Zustand(Me, New ClientEventArgs With {.Nachricht = resp})
                                         End Sub), Nothing)
        Loop
      End Sub
    
    End Class


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Montag, 15. Januar 2018 18:47
  • Hallo,

    entschuldigen sie, dass ich mich erst jetzt melde, ich war sehr beschäftigt.

    Mir ist natürlich klar, dass es einige Chatclients gibt, aber es geht mir einfach darum, so etwas mal selber zu machen und zu verstehen. Verstehen ist auch ein guter punkt, durch ihr Beispiel konnte ich sehr gut verstehen, wie sie das meinen, dass man einen Loop dennoch benutzen kann. So habe ich das meiste jetzt übertragen, nur weiß ich nicht, wie ich eine asynchrone Funktion in C++ erstellen kann, da es etwas wie Async nicht als Klassenvorlage gibt.

    Ansonsten bedanke ich mich wirklich herzlich, sie haben mir wirklich sehr weitergeholfen trotz meiner ständigen fragerei.

    Mattis Brooker

    Dienstag, 16. Januar 2018 17:05
  • Es ist dein Thread, wann und wie Du antwortest ist ganz dir überlassen. Du gehst hier keine Verpflichtung ein.

    Leider ist es so das eh sehr wenige überhaupt auf UWP fragen antworten. Bei dir kommt noch C++ hinzu was ich nicht kann und die anderen scheinbar auch nicht. Deswegen kann ich bei C++ fragen dich nur auf die MS Doku hinweisen. Wie z.B. zu dieser über "Erstellen von asynchronen Vorgängen in C++ für Windows Store-Apps". Ich würde dir empfehlen C++ Fragen im C++ Forum zu stellen. Eine Antwort solltest Du aber auch immer hier bekommen.

    Es geht auch nicht darum das IRC Protokoll nach zu bauen. Sondern sich anzuschauen wie es andere entwickeln würden. Welche Bibliotheken sie nutzen und wie sie z.B. den Stream analysieren. Ich persönlich lerne am besten aus bestehenden Code.


    Gruß Thomas
    Sage nie, ich kann es nicht - sage nur, ich kann es noch nicht!
    Dev Apps von mir: Icon für UWP,  UI Strings
    Andere Dev Apps: UWP Community Toolkit Sample App

    Dienstag, 16. Januar 2018 18:12
  • Hi Mattis,
    async und await sind Sprachelemente in Cä#.NET, VB.NET. Sie entsprechen keiner 1:1 Zuordnung zu Klassen des Frameworks.

    Vereinfacht ist das async-Schlüsselwort eine "Kennzeichnung" einer Methode, die asynchron abzuarbeitende Inhalte enthalten kann. Wenn eine mit async gekennzeichnete Methode asynchrone Inhalte hat, dann wird die Methode als eine Funktion kompiliert, die entweder Task (für void) oder Task<T> (für eine Funktion, wobei T der Rückgabetype ist) zurückliefert.

    Vereinfacht dargestellt, implementiert await ein Warten auf den Abschluss einer asynchronen Methode. 

    Hier mal eine Demo in einer Windows Anwendung (nicht UWP) zur Verdeutlichung für den einfachen Fall des Aufrufes eine Methode ohne Rückgabewert (void), die asynchron abgearbeitet wird.

      internal class Demo
      {
        internal void Execute()
        {
          Console.WriteLine($"HautpThread: {Thread.CurrentThread.ManagedThreadId}");
          // Nutzung von async await
          Sub1();
          // Nutzung von async await
          Sub2();
          // alternative Möglichkeiten
          Sub3();
        }
    
        async void Sub1()
        {
          // asynchrone Methode aufrufen und auf deren Ende warten
          await Task.Run(new Action(SubA));
        }
    
        async void Sub2()
        {
          // asynchrone Methode aufrufen 
          Task t = Task.Run(new Action(SubA));
          // hier Befehle im Haupt-Thread parallel zu Sub A ausführen
          // ...
          // und auf deren Ende warten
          await t;
        }
    
        // Objekt zum Melden des Abschlusses des parallelen Threads
        ManualResetEvent mreSub3 = new ManualResetEvent(false);
        void Sub3()
        {
          // asynchrone Methode aufrufen 
          Thread t = new Thread(new ParameterizedThreadStart(SubA));
          t.Start();
          // hier Befehle im Haupt-Thread parallel zu Sub A ausführen
          // ...
          // und auf deren Ende warten
          mreSub3.WaitOne();
        }
    
        void SubA()
        {
          // asynchron abzuarbeitende Anweisungen
          Console.WriteLine($"paralleler Thread: {Thread.CurrentThread.ManagedThreadId}");
        }
    
        void SubA(object state)
        {
          // asynchron abzuarbeitende Anweisungen
          Console.WriteLine($"paralleler Thread: {Thread.CurrentThread.ManagedThreadId}");
          // Ende melden
          mreSub3.Set();
        }
      }
    }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks


    Mittwoch, 17. Januar 2018 05:44
  • gut, das heißt für mich in C++, da es async so nicht gibt, dass ich einfach eine Funktion mit Task<void> mache? Wenn ja, dann weiß ich nicht was ich in der Funktion zurückgeben soll, da der Compiler wohl ohne rückgabewert nicht zufrieden ist. 

    Mittwoch, 17. Januar 2018 13:25
  • In deinem Fall brauchst Du nichts zurückgeben und auch auf nichts warten.

    Sobald die UWP App startet wird ein weiter Thread erstellt der neben der App mit läuft. In diesem Thread kannst Du dann mit einem Loop immer wieder den Stream analysieren ob neue Nachrichten eingegangen sind. Sind neue Nachrichten eingegangen, legst Du diese in einer Liste ab. Deine UWP App reagiert auf Veränderungen in der Liste und aktualisiert die UI. Du könntest natürlich auch aus dem Thread die UI aktualisieren dafür müsstest Du aber einen Dispatcher nehmen. Ich persönlich würde die Liste bevorzugen das dies viel übersichtlicher ist.


    Gruß Thomas
    Sage nie, ich kann es nicht - sage nur, ich kann es noch nicht!
    Dev Apps von mir: Icon für UWP,  UI Strings
    Andere Dev Apps: UWP Community Toolkit Sample App

    Mittwoch, 17. Januar 2018 13:53
  • leider wird das Programm so aber nicht compiled. Hier der Code der Funktion:
    concurrency::task<void> startReceiving()
    		{
    			DataReader ^dataReader = ref new DataReader(this->socket->InputStream);
    			concurrency::create_task(dataReader->LoadAsync(sizeof(unsigned int))).then([=](unsigned int bytesLoaded) {
    				if (bytesLoaded > 0)
    				{
    					unsigned int stringLength = dataReader->ReadUInt32();
    					concurrency::create_task(dataReader->LoadAsync(stringLength)).then([=](unsigned int bytesLoaded) {
    						String ^response = dataReader->ReadString(bytesLoaded);
    
    						Run ^run = ref new Run();
    						run->Text = response;
    						Paragraph ^paragraph = ref new Paragraph();
    
    						paragraph->Inlines->Append(run);
    
    						this->richTextBlock->Blocks->Append(paragraph);
    					});
    				}
    			});
    		}
    Da sagt der Compiler:  "Universal_App_1::MainPage::startReceiving": Muss einen Wert zurückgeben

    Mittwoch, 17. Januar 2018 14:16
  • Schau dir mal das an Simple example of threading in C++

    Gruß Thomas
    Sage nie, ich kann es nicht - sage nur, ich kann es noch nicht!
    Dev Apps von mir: Icon für UWP,  UI Strings
    Andere Dev Apps: UWP Community Toolkit Sample App

    Mittwoch, 17. Januar 2018 14:24
  • wenn ich exakt das mache, was in dem Beispiel steht, wir die Funktion nicht als Argument akzeptiert. So langsam verzweifle ich hier echt, und das bei so einer simplen Anwendung.
    Mittwoch, 17. Januar 2018 15:12
  • Hi Mattis,
    ich verstehe das so, dass startReceiving in Deiner Deklaration eine Funktion mit Rückgabewert Task<void> ist. In C# wäre dafür ein return ret; erforderlich, wobei ret von Tast.Run(... als Rückgabewert erhalten wird.

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Mittwoch, 17. Januar 2018 15:45
  • Ich dachte jetzt, dass Task<void> nicht mehr als rückgabewert nötig ist, da ich fälschlicher weise gedacht habe, ich müsste das so machen um es als asynchronen Vorgang zu verwenden, deshalb habe ich stattdessen void genommen, weil das so ja auch in dem Beispiel gemacht wurde.
    Mittwoch, 17. Januar 2018 16:04
  • Du solltest einen neuen Thread aufmachen und die C++ Entwickler fragen wie man neben der Hauptanwendung einen weitern Thread betreiben kann. Wie schon gesagt habe ich von C++ kein Ahnung und meine Links könnten absoluter Bullshit sein :). Das hat aber mit UWP an sich nichts zu tun. Wie die Anwendung aufgebaut sein könnte, wurde denke ich mal ausreichend besprochen. Jetzt geht es um die Umsetzung in C++

    Gruß Thomas
    Sage nie, ich kann es nicht - sage nur, ich kann es noch nicht!
    Dev Apps von mir: Icon für UWP,  UI Strings
    Andere Dev Apps: UWP Community Toolkit Sample App

    Mittwoch, 17. Januar 2018 16:16
  • Hi Mattis,
    in C# und VB entscheidet der Compiler bei Vorhandensein des Schlüsselworte async, ob der deklarierte Rückgabetyp zurückgegeben wird oder Task bzw. Task<T>. Wenn es in der Methode await's gibt dann wird Task zurückgegeben.

    Der Aufrufer kann dann entscheiden, ober er synchron oder asynchron (mit await) aufruft.

    Das alles wird aber durch umfangreichen Code durch den Compiler realisiert. In C++ hast Du diese Unterstützung nicht und musst das selbst machen, d.h. entscheiden, was Deine Methode genau machen soll.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Mittwoch, 17. Januar 2018 16:17
  • Da das ganze hier zu nichts führt und außerdem gar nichts mehr mit dem grundproblem zu tun hat und ich das Prinzip ja verstanden habe, werde ich das ganze nun abschliessen und das VB Beispiel als Antwort kennzeichnen, trotzdem danke für die Hilfe.
    Mittwoch, 17. Januar 2018 18:07