Forum: PC-Programmierung c# TCPListener fuer viele Clients


von csharper (Gast)


Lesenswert?

Hallo in die Runde,

folgender Code funktioniert nicht wie gewünscht. Es scheint so, als wenn 
der Backgroundworker nicht wirklich vom Mainthread getrennt ist.

Wie kann ich meinen Code so umschreiben, dass das Programm 2 Threads 
hat?

1 Thread nimmt nur Verbindungen an und legt diese in einer Sammlung an

der 2 Thread geht diese Verbindungen zyklisch durch und schaut was für 
Anfragen reingekommen ist und bearbeitet diese?

Hier mein Codevorschlag, der sich gegenseitig blockt.
1
    private static List<ClientHandle> Clients; //Sammelpool aller Clients
2
    private static BackgroundWorker handlethread;
3
    
4
    static void handlethread_DoWork(object sender, DoWorkEventArgs e)
5
    {
6
      while (e.Cancel == false) 
7
      {
8
        if(Clients.Count > 0)
9
        {
10
          foreach(ClientHandle client in Clients)
11
          {
12
            client.ReceiveData();
13
            // mache was mit dem empfangenen Daten
14
            client.SendData("hier die Antwort");
15
            
16
            
17
            //wenn irgendwas bestimmtes passiert, dann hau den client raus 
18
            client.CloseConnection();
19
          }
20
        }
21
        else //wenn es noch keine Clients gibt, dann warte nochma ne Sekunde
22
        {
23
          System.Threading.Thread.Sleep(1000);
24
        }
25
      }
26
    }
27
28
    static void handlethread_ProgressChanged(object sender, ProgressChangedEventArgs e)
29
    {
30
      Console.WriteLine(e.UserState as String);
31
    }
32
    
33
    public static void Main(string[] args)
34
    {
35
      Console.WriteLine("Server wird gestartet und soll auf Port 1337 lauschen");
36
      
37
      Clients = new List<ClientHandle>();
38
      TcpListener server=null;
39
      
40
      handlethread = new BackgroundWorker(); //hier werden alle Clients der Reihe nach abgefragt und die Anfragen bearbeitet
41
        handlethread.DoWork += handlethread_DoWork;
42
        handlethread.WorkerReportsProgress = true;
43
        handlethread.ProgressChanged += handlethread_ProgressChanged;
44
        
45
        handlethread.RunWorkerAsync();
46
        
47
      try
48
        {
49
          const int port = 1337;
50
          server = new TcpListener(IPAddress.Any, port);
51
          // Start listening for client requests.
52
          server.Start();
53
          // Enter the listening loop.
54
          while(true) 
55
          {
56
            Console.Write("Waiting for a connection... ");
57
            
58
            ClientHandle clienthandle = new ClientHandle(server.AcceptTcpClient()); //warte auf eingehende Verbindungen und lege diese in neuer clienthandleklasse ab
59
            
60
            Clients.Add(clienthandle); //in die Clientliste aufnehmen.
61
             Console.WriteLine("New Connection arrived");
62
             Console.WriteLine("Total Clients right now: " + Clients.Count.ToString());
63
          }
64
          
65
        }
66
        catch(SocketException e)
67
        {
68
          Console.WriteLine("SocketException: {0}", e);
69
        }
70
        finally
71
        {
72
           server.Stop();
73
        }
74
        
75
        Console.WriteLine("\nHit enter to continue...");
76
        Console.Read();
77
           
78
    }

von ... (Gast)


Lesenswert?

Das ist kein gutes Design. Ich mache das immer so:

Der erste Thread hört mit dem TCPListener an dem Port und wartet bis 
eine Verbindung aufgebaut wird. Dann wird für jede neue Verbindung ein 
Thread gestartet, der die ankommenden Daten bearbeitet. In dem 
Lese-Thread solltest du blockierend lesen, also einfach warten bis Daten 
kommen.

Wichtig ist noch Thread-Synchronisation beachten falls notwendig, d.h. 
locks verwenden wenn Daten gleichzeitig verändert werden könnten und das 
nicht thread-sicher möglich ist.

von Dirk (Gast)


Lesenswert?


von Fabian (Gast)


Lesenswert?

Ich bin gerade mit einem ähnlichen Thema beschäftigt. Ich habe einen 
Proxy in C# geschrieben, der TCP Verbindungen annimmt und weiterleitet. 
Dieser dient später dazu, Geräte im Heimnetz aus dem Internet heraus 
erreichbar zu machen, ohne das die IP bekannt sein muss und vorallem 
ohne das Portfreigaben etc. eingerichtet werden müssen. Ähnlich macht es 
TeamViewer auch.

Fakt ist, dass das Programm nicht Thread basierend laufen kann, da die 
Anzahl der Threads begrenzt ist. Daher kommen Async sockets zum Einsatz. 
Aber auch da ist mir eine stabile Implementierung bisher leider noch 
nicht gelungen. (Was echt frustrierend ist). Ich habe bisher auch noch 
kein Beispiel gefunden, was wirklich stabil arbeitet. Mein Extremtest 
ist meist auf dem Rechner, auf dem auch das Programm läuft, mittels 
Browser direkt über 127.0.0.1 eine TCP Verbindung aufzubauen und F5 
gedrückt zuhalten. ;-) Ja, dass ist Brutal, muss aber meiner Meinung 
nach klappen, wenn später > 1000 Clients gehandled werden sollen.

Hat jemand vllt. hier eine Quelle für ein Beispiel, was zuverlässig 
funktioniert?

Danke!

von Klaus P. (Gast)


Lesenswert?

Fabian schrieb:
> Hat jemand vllt. hier eine Quelle für ein Beispiel, was zuverlässig
> funktioniert?

Nicht direkt, aber unter dem Stichwort "Service Bus" sollten Hinweise zu 
finden sein (wenn man die ganzen Links zu Azure herausgefiltert hat).

von c-hater (Gast)


Lesenswert?

Fabian schrieb:

> Fakt ist, dass das Programm nicht Thread basierend laufen kann, da die
> Anzahl der Threads begrenzt ist. Daher kommen Async sockets zum Einsatz.

OMG...

Async sockets sind letztlich auch nix anderes als Threads. Der 
Unterschied ist bloß, dass du sie nicht selber erzeugen musst, sondern 
dass das irgendwo "hinter den Kulissen" passiert.

von csharper (Gast)


Lesenswert?

Dirk schrieb:
> Hallo, ich empfehle Dir erstmal den Blog
> https://blog.stephencleary.com/2013/05/taskrun-vs-...

vielen Dank. Soweit würde ich das schon einmal richtung Tasks 
umschreiben.

... schrieb:
> Das ist kein gutes Design. Ich mache das immer so:
>
> Der erste Thread hört mit dem TCPListener an dem Port und wartet bis
> eine Verbindung aufgebaut wird. Dann wird für jede neue Verbindung ein
> Thread gestartet, der die ankommenden Daten bearbeitet. In dem
> Lese-Thread solltest du blockierend lesen, also einfach warten bis Daten
> kommen.
>
> Wichtig ist noch Thread-Synchronisation beachten falls notwendig, d.h.
> locks verwenden wenn Daten gleichzeitig verändert werden könnten und das
> nicht thread-sicher möglich ist.

Wie sehen die anderen das? Ist das Design wirklich murks? Der Server 
soll ähnlich wie ein Chatserver funktionieren. Also ein Client schreibt 
etwas und der Server soll das allen Clients mitteilen.

Wenn ich jetzt für jeden Client einen eigenen Thread habe müsste ich ja 
immer jeden Clienthread invoken und anschließend dort die Nachricht 
übergeben. Wird das so gemacht?

Fabian schrieb:
> Ich bin gerade mit einem ähnlichen Thema beschäftigt. Ich habe
> einen
> Proxy in C# geschrieben, der TCP Verbindungen annimmt und weiterleitet.
> Dieser dient später dazu, Geräte im Heimnetz aus dem Internet heraus
> erreichbar zu machen, ohne das die IP bekannt sein muss und vorallem
> ohne das Portfreigaben etc. eingerichtet werden müssen. Ähnlich macht es
> TeamViewer auch.
>
> Fakt ist, dass das Programm nicht Thread basierend laufen kann, da die
> Anzahl der Threads begrenzt ist. Daher kommen Async sockets zum Einsatz.
> Aber auch da ist mir eine stabile Implementierung bisher leider noch
> nicht gelungen. (Was echt frustrierend ist). Ich habe bisher auch noch
> kein Beispiel gefunden, was wirklich stabil arbeitet. Mein Extremtest
> ist meist auf dem Rechner, auf dem auch das Programm läuft, mittels
> Browser direkt über 127.0.0.1 eine TCP Verbindung aufzubauen und F5
> gedrückt zuhalten. ;-) Ja, dass ist Brutal, muss aber meiner Meinung
> nach klappen, wenn später > 1000 Clients gehandled werden sollen.
>
> Hat jemand vllt. hier eine Quelle für ein Beispiel, was zuverlässig
> funktioniert?
>
> Danke!

Genau so habe ich tatsächlich auch getestet. Ich schreibe mir jetzt 
wirklich einen TCP Client und nehme nicht den Browser. Ich vermute, dass 
der irgendwie kein passendes Abschlusszeichen sendet und mein Code daher 
ewig im Read hängt.

von Keiner N. (nichtgast)


Lesenswert?

csharper schrieb:
> Hallo in die Runde,
>
> folgender Code funktioniert nicht wie gewünscht. Es scheint so, als wenn
> der Backgroundworker nicht wirklich vom Mainthread getrennt ist.
>

Kannst du das bitte mal genauer definieren? Was funktioniert denn nicht 
wie gewünscht.
Das einzige was ich sehen kann ist, dass handlethread_ProgressChanged 
nicht aufgerufen wird, weil du ReportProgress in deiner DoWork nicht 
aufrufst.

von csharper (Gast)


Lesenswert?

Keiner N. schrieb:
> csharper schrieb:
>> Hallo in die Runde,
>>
>> folgender Code funktioniert nicht wie gewünscht. Es scheint so, als wenn
>> der Backgroundworker nicht wirklich vom Mainthread getrennt ist.
>>
>
> Kannst du das bitte mal genauer definieren? Was funktioniert denn nicht
> wie gewünscht.
> Das einzige was ich sehen kann ist, dass handlethread_ProgressChanged
> nicht aufgerufen wird, weil du ReportProgress in deiner DoWork nicht
> aufrufst.

Tatsächlich scheint in meinem obigen Code gar kein Fehler enthalten zu 
sein.

Mein Problem war wohl die Receivedata Funktion:

Erster Versuch war(blockiert und freezed den Prozess):
1
String data = null;
2
        
3
        using (StreamReader reader = new StreamReader(stream))
4
        {
5
          try
6
          {
7
            return reader.ReadToEnd();
8
          }
9
          catch(Exception err)
10
          {
11
            return err.Message;
12
          }
13
        
14
        }

Codesnippet, welcher funktioniert:
1
 Byte[] bytes;
2
          NetworkStream ns = stream;
3
        if(client.ReceiveBufferSize > 0)
4
        {
5
               bytes = new byte[client.ReceiveBufferSize];
6
               ns.Read(bytes, 0, client.ReceiveBufferSize);             
7
               return Encoding.ASCII.GetString(bytes); //the message incoming
8
          }
9
        return "nix";

von csharper (Gast)


Lesenswert?

Hier übrigens mein neuer Code, der auch funktioniert. Vielleicht hilft 
das ja irgendwem:
1
    private static List<Task> Clients; //Theardsicher Sammelpool aller Clients
2
    
3
    static void handletask(ClientHandle Client)
4
    {
5
      while(Client.IsAvailable())
6
      {
7
        Client.SendData("Echo:" + Client.ReceiveData());
8
      }
9
    }
10
    
11
    public static void Main(string[] args)
12
    {
13
      Console.WriteLine("Server wird gestartet und soll auf Port 1337 lauschen");
14
      
15
      Clients = new List<Task>();
16
      TcpListener server=null;
17
      
18
      try
19
        {
20
          const int port = 1337;
21
          server = new TcpListener(IPAddress.Any, port);
22
          // Start listening for client requests.
23
          server.Start();
24
          // Enter the listening loop.
25
          while(true) 
26
          {
27
            Console.Write("Waiting for a connection... ");
28
            
29
            var tcpclient = server.AcceptTcpClientAsync();
30
            tcpclient.Wait();
31
            
32
            ClientHandle clienthandle = new ClientHandle(tcpclient.Result); //wait 4 incoming clients and make a new thread to handle their requests
33
            
34
            var t = Task.Run(() => handletask(clienthandle));
35
            
36
            Clients.Add(t); //in die Clientliste aufnehmen.
37
             Console.WriteLine("New Connection arrived");
38
        
39
             for (int i = 0; i < Clients.Count -1; i++) 
40
             {
41
               if (Clients[i].IsCompleted) 
42
               {
43
                 Clients.RemoveAt(i);
44
               }
45
             }
46
         
47
             Console.WriteLine("Total Clients right now: " + Clients.Count.ToString());
48
          }
49
          
50
        }
51
        catch(SocketException e)
52
        {
53
          Console.WriteLine("SocketException: {0}", e);
54
        }
55
        finally
56
        {
57
           // Stop listening for new clients.
58
           server.Stop();
59
        }
60
        
61
        Console.WriteLine("\nHit enter to continue...");
62
        Console.Read();
63
           
64
    }

von Dirk (Gast)


Lesenswert?

Hallo,

>Wie sehen die anderen das? Ist das Design wirklich murks? Der Server
>soll ähnlich wie ein Chatserver funktionieren. Also ein Client schreibt
>etwas und der Server soll das allen Clients mitteilen.

Mit diesen Informationen würde ich sagen deine Implementierung ist nicht 
gut gewählt.

Ich vermute dein Server/Host ist eio Windowsrechner, dann emfpehle ich 
Dir WCF, ob SelfHosted oder direkt im System gehostet entscheidet 
überwiegend die Stabiliätsfrage.

Hier ein Beispiel, aber du findest unzählige Beispiele zu diesem Thema.

https://www.codeproject.com/Articles/25261/A-WCF-WPF-Chat-Application

von Sheeva P. (sheevaplug)


Lesenswert?

... schrieb:
> Das ist kein gutes Design. Ich mache das immer so:
>
> Der erste Thread hört mit dem TCPListener an dem Port und wartet bis
> eine Verbindung aufgebaut wird. Dann wird für jede neue Verbindung ein
> Thread gestartet, der die ankommenden Daten bearbeitet. In dem
> Lese-Thread solltest du blockierend lesen, also einfach warten bis Daten
> kommen.
>
> Wichtig ist noch Thread-Synchronisation beachten falls notwendig, d.h.
> locks verwenden wenn Daten gleichzeitig verändert werden könnten und das
> nicht thread-sicher möglich ist.

Das ist die ebenso klassische wie korrekte Lösung, und ich verstehe 
nicht, wie Dir jemand dafür ein "nicht lesenswert" geben kann. Es fehlen 
nur zwei Hinweise: erstens, daß die maximale Anzahl der Threads 
konfigurierbar sein sollte, um möglichen DoS-Angriffen entgegenzuwirken, 
und zweitens, daß die Connectionhandler aus demselben Grund einen 
Timeout haben sollten.

von Martin K. (martin_k662)


Lesenswert?

c-hater schrieb:
> Async sockets sind letztlich auch nix anderes als Threads. Der
> Unterschied ist bloß, dass du sie nicht selber erzeugen musst, sondern
> dass das irgendwo "hinter den Kulissen" passiert.

Das ist so nicht richtig. Bei asynchroner Programmierung wird meist mit 
einer eventloop gearbeitet, genau das gleiche macht man, wenn man in C 
mit epoll (bevorzugt), poll oder select programmiert. Die Verwaltung von 
TCP-Verbindungen ist nicht wirklich aufwändig, sodass man das gut in nur 
einem Thread erledigen kann.

Was allerdings ein legitimes Mittel ist: Die eigentliche Arbeit 
(Berechnungen etc) in einen Threadpool auszulagern, das minimiert die 
Stellen, an denen du zwischen Threads und zwischen dem 
Verbindungs-Thread und den Worker-Threads synchronisieren musst.

Hier mal ein Tutorial für epoll() 
https://kovyrin.net/2006/04/13/epoll-asynchronous-network-programming/.
Das Konzept ist auch in C#, Python etc. ähnlich.

von c-hater (Gast)


Lesenswert?

Martin K. schrieb:

> c-hater schrieb:
>> Async sockets sind letztlich auch nix anderes als Threads. Der
>> Unterschied ist bloß, dass du sie nicht selber erzeugen musst, sondern
>> dass das irgendwo "hinter den Kulissen" passiert.
>
> Das ist so nicht richtig.

Doch, ist es. Auf jeden Fall im Falle von C# in der .Net-Umgebung und 
das war das Thema...

> Bei asynchroner Programmierung wird meist mit
> einer eventloop gearbeitet

Eine EventLoop ist NICHT ansynchron, kann es nicht sein. Allein der 
Begriff "loop" darin zeigt das nur zu deutlich. Eine Schleife ist 
nämlich eine Kontrollstruktur, die es nur synchron geben kann.

Natürlich können asynchrone Zustandsänderungen auf den Scheiss in der 
Schleife einwirken, aber nur, wenn diese Zustandsänderungen asychron 
generiert werden (also in Threads oder ISRs) UND  die Schleife durch 
Polling der Zustände davon erfährt.

>  Die Verwaltung von
> TCP-Verbindungen ist nicht wirklich aufwändig, sodass man das gut in nur
> einem Thread erledigen kann.

Ja, genau deswegen machen das wohl alle modernen Betriebssysteme seit 
Jahrzehnten genau NICHT so...

Natürlich finden sich aber immer wieder dumme Anfänger, die den Support 
moderner OS nicht nutzen, weil sie mit der Programmierung paralleler 
Abläufe schlicht hoffnungslos überfordert sind und deshalb irgendwelche 
Polling-Ansätze nutzen, die die OS' seit Jahrzehnten allein deshalb 
mitschleppen, um eben diesen Typen auch eine kleines Erfolgserlebnis zu 
ermöglichen...

Finster, aber die blanke Wahrheit.

von Martin K. (martin_k662)


Lesenswert?

c-hater schrieb:
> Doch, ist es. Auf jeden Fall im Falle von C# in der .Net-Umgebung und
> das war das Thema...

Er benutzt die Threads ausschließlich, um die Workload abzugeben - wie 
ich oben schrieb.

Hier: 
https://www.filipekberg.se/2013/01/16/what-does-async-await-generate/
Und hier: 
https://blogs.msdn.microsoft.com/seteplia/2017/11/30/dissecting-the-async-methods-in-c/ 
kannst du dazu nachlesen. Genau so funktionieren auch async Sockets in 
.NET.

Hier das gleiche nochmal in Typescript: 
https://basarat.gitbooks.io/typescript/docs/async-await.html

Im Endeffekt baust du eine Statemachine, und gibst dem OS eine Liste von 
Filedeskriptoren die es monitoren soll und (ich bleibe jetzt mal bei 
epoll_wait) dein syscall wird solange blockieren, bis auf einem deiner 
FD ein Ereignis eintritt.

c-hater schrieb:
> Finster, aber die blanke Wahrheit.

Genau deshalb macht das auch große Software wie nginx 
(http://nginx.org/en/docs/events.html) so. Da sitzen offensichtlich nur 
dumme Anfänger...

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.