none
TCPClient.Client zu Stream, Synchron, Asynchron RRS feed

  • Frage

  • Hallo,
    Verständnisfrage.
    Es gibt n Varianten, wie man eine Socketverbindung erstellt.
    Synchron, asynchron, ja.
    await, Task, usw.
    Ich habe einen Request und einen Response.
    Weiter kann es gehen, wenn der Response vorhanden ist.
    Also benötige ich in diesem Fall Synchron.
    Um sicherzustellen, das der ReceiveBuffer leer ist, mache ich das wie folgt. (siehe unten)
    A) Ist das richtig?
    B) Welche Kriterien gelten, wenn man was wie benutzt.
       B1) Stream   -- nur char[]
       B2) Socket   -- nur Byte[]
       Vor-/Nachteile.
       Für meinen Zweck funktionieren beide, aber irgendwas muss ja sein, dass Microsoft mehrere Varianten anbietet.
    C) Wenn asynchron, würde das Programm weitermachen, müsste ich dann einen Eventhandler einbauen,
       der mir die Antwort mitteilt.
       C1) Ein Beispiel wäre hier sehr hilfreich.
       C2) @Peter, Dein Beispiel wäre da super, evtl. Winform oder WPF DesktopApp.
                   Vielleicht kannst Du das aufzeigen, wie man das macht.
         
      
    Es wäre sehr nett, Infos, Erläuterungen zu bekommen.
    Viele Grüße Oliver
    var buffer = new byte[100000];
    // delete all from receive buffer, is must be synchron.
    while (TClient.Available > 0)
    {
    	int length = TClient.Client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
    }
    Array.Clear(buffer, 0, buffer.Length);
    
    byte[] buf1 = Encoding.Default.GetBytes(message);
    TClient.Client.Send(buf1);
    
    
    var buffer = new char[1000];
    // delete all from receive buffer, is must be synchron.
    while (TClient.Available > 0)
    {
    	int length = SReader.Read(buffer, 0, buffer.Length);
    }
    Array.Clear(buffer, 0, buffer.Length);
    
    SWriter.WriteLine(message);
    

    Samstag, 12. August 2017 10:48

Antworten

  • Hi Oliver,
    sich auf eine Begriffswelt zu einigen, ist die Grundlage für ein gegenseitiges Verstehen, was uns vermutlich noch fehlt. Ich verstehe z.B. nicht die Tätigkeit "erfolgen", die Du benutzt. Dabei bleibt unklar, wer was genau macht.

    Eine Anforderung (Request) fordert den Server auf, etwas zu tun. Was der Server zu tun hat bestimmen die Parameter, die die Anforderung enthalten kann. In welcher Form die Parameter übergeben werden, ist ein nachrangiges Thema.

    Eine Antwort (Response) ist die Antwort des Servers auf die Anforderung. In welcher Form diese Antwort zurückgegeben wird, ist auch nachrangig.

    Wie ich etwas implementiere, hängt wesentlich von der Aufgabenstellung ab, die ich zu Deinen Fragen nicht kenne. Im Rahmen des Forums kann ich nur zu konkreten Fragen meine Meinung sagen, nicht zur prinzipiellen Realisierung eines komplexen Projektes ohne Kenntnis aller Vorgaben.

    EDIT 15.8.17 07:01: Beispiel einer möglichen Lösung (ohne Fehlerbehandlung) hinzugefügt

    Server XAML:

    <Window x:Class="WpfApp1CS.Window09"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1CS"
            mc:Ignorable="d"
            Title="Server" Height="300" Width="300">
      <Window.Resources>
        <local:Window09ServerVM x:Key="vm"/>
      </Window.Resources>
      <StackPanel DataContext="{StaticResource vm}">
        <Button Content="Start Client" Command="{Binding Cmd}" Margin="5"/>
        <Label Content="Protokoll"/>
        <ListBox ItemsSource="{Binding Protokoll}" Margin="5"/>
      </StackPanel>
    </Window>

    Client XAML:

    <Window x:Class="WpfApp1CS.Window09Client"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1CS"
            mc:Ignorable="d"
            Title="{Binding Name}" Height="300" Width="300">
      <StackPanel>
        <TextBox Margin="5" Text="{Binding Req}"/>
        <Button Content="Senden" Margin="5" Command="{Binding Cmd}"/>
      </StackPanel>
    </Window>

    ViewModels:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Input;
    using System.Windows.Threading;
    
    namespace WpfApp1CS
    {
      /// <summary>
      /// ViewModel für Server
      /// </summary>
      public class Window09ServerVM
      {
        /// <summary>
        /// Portnummer, auf die der Server horcht
        /// </summary>
        private int _listeningPort = 5000;
        /// <summary>
        /// Dispatcher für späteren thread-übergreifenden Zugriff merken
        /// </summary>
        private Dispatcher disp = Dispatcher.CurrentDispatcher;
        /// <summary>
        /// Konstruktor mit Bindung der Auflistung und start des Servers
        /// </summary>
        public Window09ServerVM()
        {
          cvs.Source = col;
          StartServer();
        }
        /// <summary>
        /// Datenquelle für Anzeige
        /// </summary>
        CollectionViewSource cvs = new CollectionViewSource();
        /// <summary>
        /// Auflistung mit Protokolldaten
        /// </summary>
        ObservableCollection<string> col = new ObservableCollection<string>();
        /// <summary>
        /// Fortlaufende Nummer des gestarteten Clients
        /// </summary>
        int clientNr = 1;
        /// <summary>
        /// Sicht auf die Protokolldaten für Anzeige in ListBox
        /// </summary>
        public ICollectionView Protokoll { get { return cvs?.View; } }
        /// <summary>
        /// Eigenschaft für Command-Bindung
        /// </summary>
        public ICommand Cmd { get { return new RelayCommand(CmdExec); } }
        /// <summary>
        /// Methode, die bei Command-Auslösung abzuarbeiten ist und neuen Client anzeigt
        /// </summary>
        /// <param name="obj">CommandParameter</param>
        private void CmdExec(object obj) { (new Window09Client() { DataContext = new Window09ClientVM() { Name = $"Client {clientNr++}" } }).Show(); }
        /// <summary>
        /// Starten des Servers
        /// </summary>
        private void StartServer()
        {
          IPAddress ipAddre = IPAddress.Loopback;
          TcpListener server = new TcpListener(ipAddre, _listeningPort);
          server.Start();
          LogMessage("Server is running");
          LogMessage("Listening on port " + _listeningPort);
          LogMessage("Waiting for connections...");
          // Asynchrones Warten auf Verbindungsaufnahme durch einen Client
          try { server.BeginAcceptTcpClient(DoAccept, server); }
          catch (Exception exp) { LogMessage(exp.ToString()); }
        }
        /// <summary>
        /// Asynchrone Methode, die nach dem Verbindungsaufbau Daten empfängt
        /// </summary>
        /// <param name="ar"></param>
        private void DoAccept(IAsyncResult ar)
        {
          // Puffer für auszugebende Zeichen
          StringBuilder sb = new StringBuilder();
          // Server-Objekt ermitteln, welches auf den Verbindungsaufbau gewartet hat
          TcpListener server = (TcpListener)(ar.AsyncState);
          // TcpClient ermitteln, mit dem die daten empfangen werden können
          TcpClient client = server.EndAcceptTcpClient(ar);
          // Stream für das Einlesen der Bytes besorgen
          using (NetworkStream stream = client.GetStream())
          {
            // Byte-Puffer bereitstellen
            byte[] buf = new byte[256];
            // erste Byte-Portion bis zur maximalen Puffergröße lesen
            int i = stream.Read(buf, 0, buf.Length);
            while (i != 0)
            {
              // Bytes in Zeichen umwandeln und dem Ausgabepuffer hinzufügen
              sb.Append(Encoding.ASCII.GetString(buf, 0, i));
              // nächste Byte-Portion bis zur maximalen Puffergröße lesen
              i = stream.Read(buf, 0, buf.Length);
            }
          }
          // Empfangene Anforderung zur Anzeige bringen
          LogMessage(sb.ToString());
          // auf nächsten Verbingsaufbau warten
          server.BeginAcceptTcpClient(DoAccept, server);
        }
        /// <summary>
        /// Methode zur Einreihung eines Text incl. Zeitstempel an den Anfang der Auflistung
        /// </summary>
        /// <param name="msg">anzuzeigender Text</param>
        private void LogMessage(string msg) =>
          disp.Invoke(() => col.Insert(0, $"{DateTime.Now.ToLongTimeString()} - {msg}"));
      }
      /// <summary>
      /// ViewModel für Client
      /// </summary>
      public class Window09ClientVM
      {
        /// <summary>
        /// Name des Clients
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Zeichenkette aus TextBox, die als Anforderung an den Server gesendet wird
        /// </summary>
        public string Req { get; set; }
        /// <summary>
        /// IP-Adresse des Servers
        /// </summary>
        private byte[] _host = new byte[] { 127, 0, 0, 1 };
        /// <summary>
        /// Portnummer, auf die der Server horcht
        /// </summary>
        private int _port = 5000;
        /// <summary>
        /// fortlaufende Nummer der Nachricht
        /// </summary>
        private int msgNr;
        /// <summary>
        /// Generator für Zufallszahlen
        /// </summary>
        private Random rnd = new Random();
        /// <summary>
        /// Eigenschaft für Command-Bindung
        /// </summary>
        public ICommand Cmd { get { return new RelayCommand(CmdExec); } }
        /// <summary>
        /// Methode, die bei Command-Auslösung abzuarbeiten ist und eine Verbindung startet
        /// </summary>
        /// <param name="obj">CommandParameter</param>
        private void CmdExec(object obj)
        {
          // Eingabe erforderlich für zu sendenden Text
          if (string.IsNullOrEmpty(Req)) return;
          // Nachrichtennummer rücksetzen
          msgNr = 1;
          // Verbindungsaufnahme
          TcpClient client = new TcpClient();
          client.BeginConnect(new IPAddress(_host), _port, DoSend, client);
        }
        /// <summary>
        /// Asynchrone Methode, die nach dem Verbindungsaufbau Daten sendet
        /// </summary>
        /// <param name="ar"></param>
        private void DoSend(IAsyncResult ar)
        {
    
          // Bytes der zu sendenden Anforderung
          byte[] data = Encoding.ASCII.GetBytes($"Anforderung {msgNr++} von {Name}: {Req}");
          // TcpClient holen, der die Verbindung aufgenommen hat
          TcpClient client = (TcpClient)(ar.AsyncState);
          // Anforderung senden
          using (NetworkStream stream = client.GetStream()) { stream.Write(data, 0, data.Length); }
          // zufällige Zeit warten bis zum Senden der nächsten Anforderung
          Thread.Sleep(rnd.Next(500, 2000));
          // maximale Zahl von zu senden Anforderungen prüfen
          if (msgNr < 10)
          {
            // neue Verbindungsaufname
            client = new TcpClient();
            client.BeginConnect(new IPAddress(_host), _port, DoSend, client);
          }
        }
      }
    }


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



    Montag, 14. August 2017 19:21

Alle Antworten

  • Hi Oliver,
    bei synchroner Arbeitsweise wird auf das Ende der Arbeit gewartet und der Thread ist "besetzt"/"blockiert"/"in Arbeit". Bei asynchroner Arbeitsweise wird nach dem Start der asynchronen Methode im Programm weitergearbeitet und die asynchrone Methode wird parallel oder versetzt (z.B., wenn der Thread pausiert/wartet) abgearbeitet. Wenn Dein Server mehrere Clients bedienen soll, so kann eine synchrone Arbeitsweise zu Time-Outs bei anderen Clients führen, da der Server erst eine Anforderung vom nächsten Client verarbeiten kann, wenn das Ende der Verarbeitung der aktuellen Anforderung erreicht wurde. Bei asynchroner Arbeitsweise können die Anforderungen quasi parallel (verschachtelt) in einem Thread oder voll parallel in mehreren Threads verarbeitet werden. Auch ist eine gemischte Arbeitsweise möglich: asynchrone Bedienung der Requests und synchrone Verarbeitung eventuell erforderlicher Responses, ggf. im Thread, der den Request verarbeitet.

    A) Ob Du es passend zur Aufgabenstellung/Anforderung realisierst, weiß ich nicht, da ich das Pflichtenheft nicht kenne.

    B) Es gilt A). Im Pflichtenheft steht, ob eine Byte-Verarbeitung oder eine Zeichenverarbeitung (ggf. gehören zu einem Zeichen mehrere Bytes) gefordert ist.

    C) Asynchrone Arbeitsweise ist sowohl mit Threads als auch mit async möglich. In jedem Fall ist zum Ende der Arbeit einer asynchronen Methode das Ergebnis wichtig, das entweder ggf. zu prüfen oder auf das ggf. zu warten ist. Gewartet werden kann mit einem CallBack, einem Ereignis oder await.

    Beispiele auch zur asynchronen Arbeitsweise habe ich schon mehrfach gepostet. Wenn Dir diese nicht reichen, dann schreibe mal, was da fehlt. Vielleicht kann ich da etwas anpassen.


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


    Samstag, 12. August 2017 12:47
  • Beispiele auch zur asynchronen Arbeitsweise habe ich schon mehrfach gepostet. Wenn Dir diese nicht reichen, dann schreibe mal, was da fehlt. Vielleicht kann ich da etwas anpassen.



    Hallo Peter,

     

    sehr gerne schildere ich Dir die Möglichkeiten.

      

    1. Synchron bei mir zu 90%
      Request --- es wird eine Message versendet
      Response --- ich muss warten, bis eine Antwort erfolgt.
    2. Server Er weiß ja nicht wann eine Frage kommt, also Event basierend, in einem Thread - Listen
    3. Asynchron Request --- es wird eine Message versendet
      Response ---        jetzt muss ich ja im Receive Event, die Anfrage auswerten und verteilen, da es ja nicht synchron ist.     
           In meinem Programm bin ich ja weiter. Die Haupt Applikation muss jetzt die Antwort auswerten.

    Hoffe alles verstanden, was ich suche. Was reicht mir nicht? Asynchron ist sicher besser, dann hängt die Applikation nicht. Ich muss ja dann über einen Eventhandler das Ergebnis analysieren. Das kommt in Deinen bisherigen Beispiele zu kurz.

    Für Tipps, finale Beispiel wäre echt nett, sage ich jetzt schon DANKE.

     

    Viele Grüße Oliver


    Sonntag, 13. August 2017 13:27
  • Hi Oliver,
    ich verstehe Deine Schilderung nicht:

    Request --- es wird eine Message versendet - Ein Request versendet keine Nachricht. Man kann maximal in der Anforderung eine zusätzlich Info "verpacken".

    Response --- ich muss warten, bis eine Antwort erfolgt - Ein Erfolg erfolgt immer erfolgreich. Du musst da schon etwas genauer beschreiben, was gefordert ist.

    Server Er weiß ja nicht wann eine Frage kommt, also Event basierend, in einem Thread - Listen - Wie ist das zu verstehen? Ein Server wartet üblicherweise auf einen Request von Client. Für das Warten gibt es unterschiedliche Technologien, z.B. Polling (die ungünstigste), asynchron Thread starten, der wartet, Ereignis abonnieren, auf AutoResetEvent warten, await nutzen usw.

    Asynchron Request --- es wird eine Message versendet - Ein Request versendet keine Nachricht. Die Auswertung der Anforderung kann zum Versenden einer Response (= Antwort/Nachricht) führen, wenn das gefordert wird. Und diese Response kann wieder synchron oder auch asynchron ausgeführt werden. Das muss anhand der Aufgabenstellung entschieden werden.

    Response --- jetzt muss ich ja im Receive Event, die Anfrage auswerten und verteilen, da es ja nicht synchron ist. - Die Antwort (Response) kann synchron oder auch asynchron abgesetzt werden. Welche Variante genutzt wird, hängt davon ab, was sonst noch zu machen ist.

    Asynchron ist sicher besser, dann hängt die Applikation nicht. - Prozesse führen immer zum Hängen anderer Prozesse, wenn sie im gleichen Thread ausgeführt werden. Wenn also die Anzeigen in der Oberfläche aktualisiert werden sollen und auch der Bediener zu jeder Zeit die Oberflächen bedienen dürfen soll, dann dürfen parallel im gleichen (Oberflächen-) Thread keine Prozesse, insbesondere lang laufende Prozesse wie TCP-Verkehr, abgearbeitet werden.

    Ich empfehle Dir, die konkret zu realisierende Aufgabenstellung intensiver aufzubereiten und die einzelnen abzuarbeitenden Prozesse detaillierter zu spezifizieren.


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

    Sonntag, 13. August 2017 15:54
  • Hallo Peter,
    mit Request meine ich das, Du hast es ja schon so gemacht.
    Frage-Antwort halt.
    Request:<command><notification>GetContent</notification></command>
    Response:<reply><notification>GetContent</notification></Reply>

    Ich weiß nicht, was Du nicht verstehst.
    Die obigen Strings werden versendet, empfangen.
    Es sind lt. Deiner Aussage, kein Request, kein Response.
    OK, wie würdest Du das bezeichnen?
    Was ist die korrekte Bezeichnung aus Deiner Sicht.
    Das Problem ist bei Asynchron ja, ich muss auf einen Empfangshandler reagieren.
    Wie würdest Du dann es in einer WPF Applikation visualisieren?
    Wie würdest Du den Empfangshandler, Event implementieren?
    Viele Grüße Oliver
    Server:
    /// <summary>
    /// mit aufgebauter Verbindung auf Sockets warten
    /// </summary>
    /// <param name="tcpClient">client connections for TCP network services</param>
    private async void HandleConnectionAsync(TcpClient tcpClient)
    {
    tcpClient.LingerState.Enabled = false;
    string clientInfo = tcpClient.Client.RemoteEndPoint.ToString();
    LogMessage(string.Format("Got connection request from {0}", clientInfo));
    try
    {
      XElement rec = new XElement("leer");
      while (rec != null)
      {
    	rec = await sck.ReceiveSocket(tcpClient.Client);
    	LogMessage($"Receive from Client:{Environment.NewLine}{rec.ToString()}");
    	sck.SendSocket(tcpClient.Client, @"<response>" + rec.ToString() + "</response>");
      }
    }
    catch (Exception exp) { LogMessage(exp.Message); }
    finally
    {
      LogMessage(string.Format("Closing the client connection - {0}", clientInfo));
      tcpClient.Close();
    }
    }
    internal void LogMessage(string msg) => Console.WriteLine("Server: " + msg);
    }
    
    
    Client:
    LogMessage($"Connection established");
    for (int i = 1; i < 10; i++)
    {
      sck.SendSocket(s2, $"<command><notification>{Name} = {i}</notification></command>");
      XElement rec = await sck.ReceiveSocket(s2);
      LogMessage($"Receive from Server:{Environment.NewLine}{rec.ToString()}");
    }
    
    
    

    Montag, 14. August 2017 16:49
  • Hi Oliver,
    sich auf eine Begriffswelt zu einigen, ist die Grundlage für ein gegenseitiges Verstehen, was uns vermutlich noch fehlt. Ich verstehe z.B. nicht die Tätigkeit "erfolgen", die Du benutzt. Dabei bleibt unklar, wer was genau macht.

    Eine Anforderung (Request) fordert den Server auf, etwas zu tun. Was der Server zu tun hat bestimmen die Parameter, die die Anforderung enthalten kann. In welcher Form die Parameter übergeben werden, ist ein nachrangiges Thema.

    Eine Antwort (Response) ist die Antwort des Servers auf die Anforderung. In welcher Form diese Antwort zurückgegeben wird, ist auch nachrangig.

    Wie ich etwas implementiere, hängt wesentlich von der Aufgabenstellung ab, die ich zu Deinen Fragen nicht kenne. Im Rahmen des Forums kann ich nur zu konkreten Fragen meine Meinung sagen, nicht zur prinzipiellen Realisierung eines komplexen Projektes ohne Kenntnis aller Vorgaben.

    EDIT 15.8.17 07:01: Beispiel einer möglichen Lösung (ohne Fehlerbehandlung) hinzugefügt

    Server XAML:

    <Window x:Class="WpfApp1CS.Window09"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1CS"
            mc:Ignorable="d"
            Title="Server" Height="300" Width="300">
      <Window.Resources>
        <local:Window09ServerVM x:Key="vm"/>
      </Window.Resources>
      <StackPanel DataContext="{StaticResource vm}">
        <Button Content="Start Client" Command="{Binding Cmd}" Margin="5"/>
        <Label Content="Protokoll"/>
        <ListBox ItemsSource="{Binding Protokoll}" Margin="5"/>
      </StackPanel>
    </Window>

    Client XAML:

    <Window x:Class="WpfApp1CS.Window09Client"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1CS"
            mc:Ignorable="d"
            Title="{Binding Name}" Height="300" Width="300">
      <StackPanel>
        <TextBox Margin="5" Text="{Binding Req}"/>
        <Button Content="Senden" Margin="5" Command="{Binding Cmd}"/>
      </StackPanel>
    </Window>

    ViewModels:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Input;
    using System.Windows.Threading;
    
    namespace WpfApp1CS
    {
      /// <summary>
      /// ViewModel für Server
      /// </summary>
      public class Window09ServerVM
      {
        /// <summary>
        /// Portnummer, auf die der Server horcht
        /// </summary>
        private int _listeningPort = 5000;
        /// <summary>
        /// Dispatcher für späteren thread-übergreifenden Zugriff merken
        /// </summary>
        private Dispatcher disp = Dispatcher.CurrentDispatcher;
        /// <summary>
        /// Konstruktor mit Bindung der Auflistung und start des Servers
        /// </summary>
        public Window09ServerVM()
        {
          cvs.Source = col;
          StartServer();
        }
        /// <summary>
        /// Datenquelle für Anzeige
        /// </summary>
        CollectionViewSource cvs = new CollectionViewSource();
        /// <summary>
        /// Auflistung mit Protokolldaten
        /// </summary>
        ObservableCollection<string> col = new ObservableCollection<string>();
        /// <summary>
        /// Fortlaufende Nummer des gestarteten Clients
        /// </summary>
        int clientNr = 1;
        /// <summary>
        /// Sicht auf die Protokolldaten für Anzeige in ListBox
        /// </summary>
        public ICollectionView Protokoll { get { return cvs?.View; } }
        /// <summary>
        /// Eigenschaft für Command-Bindung
        /// </summary>
        public ICommand Cmd { get { return new RelayCommand(CmdExec); } }
        /// <summary>
        /// Methode, die bei Command-Auslösung abzuarbeiten ist und neuen Client anzeigt
        /// </summary>
        /// <param name="obj">CommandParameter</param>
        private void CmdExec(object obj) { (new Window09Client() { DataContext = new Window09ClientVM() { Name = $"Client {clientNr++}" } }).Show(); }
        /// <summary>
        /// Starten des Servers
        /// </summary>
        private void StartServer()
        {
          IPAddress ipAddre = IPAddress.Loopback;
          TcpListener server = new TcpListener(ipAddre, _listeningPort);
          server.Start();
          LogMessage("Server is running");
          LogMessage("Listening on port " + _listeningPort);
          LogMessage("Waiting for connections...");
          // Asynchrones Warten auf Verbindungsaufnahme durch einen Client
          try { server.BeginAcceptTcpClient(DoAccept, server); }
          catch (Exception exp) { LogMessage(exp.ToString()); }
        }
        /// <summary>
        /// Asynchrone Methode, die nach dem Verbindungsaufbau Daten empfängt
        /// </summary>
        /// <param name="ar"></param>
        private void DoAccept(IAsyncResult ar)
        {
          // Puffer für auszugebende Zeichen
          StringBuilder sb = new StringBuilder();
          // Server-Objekt ermitteln, welches auf den Verbindungsaufbau gewartet hat
          TcpListener server = (TcpListener)(ar.AsyncState);
          // TcpClient ermitteln, mit dem die daten empfangen werden können
          TcpClient client = server.EndAcceptTcpClient(ar);
          // Stream für das Einlesen der Bytes besorgen
          using (NetworkStream stream = client.GetStream())
          {
            // Byte-Puffer bereitstellen
            byte[] buf = new byte[256];
            // erste Byte-Portion bis zur maximalen Puffergröße lesen
            int i = stream.Read(buf, 0, buf.Length);
            while (i != 0)
            {
              // Bytes in Zeichen umwandeln und dem Ausgabepuffer hinzufügen
              sb.Append(Encoding.ASCII.GetString(buf, 0, i));
              // nächste Byte-Portion bis zur maximalen Puffergröße lesen
              i = stream.Read(buf, 0, buf.Length);
            }
          }
          // Empfangene Anforderung zur Anzeige bringen
          LogMessage(sb.ToString());
          // auf nächsten Verbingsaufbau warten
          server.BeginAcceptTcpClient(DoAccept, server);
        }
        /// <summary>
        /// Methode zur Einreihung eines Text incl. Zeitstempel an den Anfang der Auflistung
        /// </summary>
        /// <param name="msg">anzuzeigender Text</param>
        private void LogMessage(string msg) =>
          disp.Invoke(() => col.Insert(0, $"{DateTime.Now.ToLongTimeString()} - {msg}"));
      }
      /// <summary>
      /// ViewModel für Client
      /// </summary>
      public class Window09ClientVM
      {
        /// <summary>
        /// Name des Clients
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Zeichenkette aus TextBox, die als Anforderung an den Server gesendet wird
        /// </summary>
        public string Req { get; set; }
        /// <summary>
        /// IP-Adresse des Servers
        /// </summary>
        private byte[] _host = new byte[] { 127, 0, 0, 1 };
        /// <summary>
        /// Portnummer, auf die der Server horcht
        /// </summary>
        private int _port = 5000;
        /// <summary>
        /// fortlaufende Nummer der Nachricht
        /// </summary>
        private int msgNr;
        /// <summary>
        /// Generator für Zufallszahlen
        /// </summary>
        private Random rnd = new Random();
        /// <summary>
        /// Eigenschaft für Command-Bindung
        /// </summary>
        public ICommand Cmd { get { return new RelayCommand(CmdExec); } }
        /// <summary>
        /// Methode, die bei Command-Auslösung abzuarbeiten ist und eine Verbindung startet
        /// </summary>
        /// <param name="obj">CommandParameter</param>
        private void CmdExec(object obj)
        {
          // Eingabe erforderlich für zu sendenden Text
          if (string.IsNullOrEmpty(Req)) return;
          // Nachrichtennummer rücksetzen
          msgNr = 1;
          // Verbindungsaufnahme
          TcpClient client = new TcpClient();
          client.BeginConnect(new IPAddress(_host), _port, DoSend, client);
        }
        /// <summary>
        /// Asynchrone Methode, die nach dem Verbindungsaufbau Daten sendet
        /// </summary>
        /// <param name="ar"></param>
        private void DoSend(IAsyncResult ar)
        {
    
          // Bytes der zu sendenden Anforderung
          byte[] data = Encoding.ASCII.GetBytes($"Anforderung {msgNr++} von {Name}: {Req}");
          // TcpClient holen, der die Verbindung aufgenommen hat
          TcpClient client = (TcpClient)(ar.AsyncState);
          // Anforderung senden
          using (NetworkStream stream = client.GetStream()) { stream.Write(data, 0, data.Length); }
          // zufällige Zeit warten bis zum Senden der nächsten Anforderung
          Thread.Sleep(rnd.Next(500, 2000));
          // maximale Zahl von zu senden Anforderungen prüfen
          if (msgNr < 10)
          {
            // neue Verbindungsaufname
            client = new TcpClient();
            client.BeginConnect(new IPAddress(_host), _port, DoSend, client);
          }
        }
      }
    }


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



    Montag, 14. August 2017 19:21