Laden...

Multi-Thread-Server-Client: Inwiefern beeinflussen Threads die Geschwindigkeit des Servers?

Erstellt von Frokuss vor 6 Jahren Letzter Beitrag vor 6 Jahren 7.131 Views
F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren
Multi-Thread-Server-Client: Inwiefern beeinflussen Threads die Geschwindigkeit des Servers?

Wunderschönen guten Tag,

ich versuche derzeit eine consolenbasierte (um mich an das Thema zu tasten) Multi-Thread Server Anwendung mit Netzwerkanbindung zu schreiben. Ich habe es gegenwärtig soweit, dass sich mehrere Clients verbinden können und Daten empfangen können. Habe mich da weitesgehend an folgendes gehalten:
https://msdn.microsoft.com/de-de/library/bb979208.aspx

Dabei wird jeder TcpCient in einen eigenen Thread ausgelagert und sendet (Stream.Write) in endlosschleife Daten an den jeweiligen Client.

Nun habe ich versucht noch einen StreamReader (Ich weis, man sollte was anderes nehmen, muss mich da noch schlau machen) einzubinden und musste feststellen, dass dieser natürlich den Thread blockiert. Nach einigen Recherchen habe ich den Eindruck gewonnen, dass die nun diesen Reader wieder in einen seperaten Thread auslagern muss. Was also am Ende bedeuten würde, dass ich für jede Verbindung zwei Threads am Server habe.

Daher meine Frage, in wie fern beeinflussen zu viele Threads die Geschwindigkeit des Servers? Schließlich hätte ich dann bei 100 Anwendern ja rund 200 Threads... Könnte man irgendwie den StreamReader für alle TcpClients in einen Thread auslagern? Wie machen es Anwendungen wie WhatsApp?

Leider sind bei mir Threadering und Net/Net.Socket Anwendungen und Techniken nicht wirklich bekannt... habe versucht mich seit dem Wochenende da rein zu arbeiten und bin damit quasi krasser Noob.

Ich hoffe ihr habt ein paar nützliche Links, die mi dabei weiterhelfen könnten.

Mit freundlichen Grüßen
Frokuss

PS: Am Ende will ich noch eine MySQL Verbindung aufbauen und da alles mögliche speichern/laden. Praktisch kann ich damit schon arbeiten - nur noch nicht in Verbindung mit dem oben benannten gemacht..

D
985 Beiträge seit 2014
vor 6 Jahren

... in wie fern beeinflussen zu viele Threads die Geschwindigkeit des Servers?

Kann man pauschal nicht sagen.

Ich kann auf meinem Rechner x Anwendungen laufen lassen, ohne dass die CPU-Auslastung einen nennenswerten Ausschlag anzeigt (und in jeder Anwendung sind y Threads aktiv).

Die Anzahl der Threads ist unerheblich, es kommt darauf an, was der einzelne Thread macht.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Cool danke. Das bedeutet ich kann annehmen, dass der Server von Facebook, Whasapp, etc. auch alle Verbindungen in einem eigenen Thread ausgelagert haben, die einfach nur auf die einzelnen TcpVerbindungen horchen und in anderen einfach nur senden?

Bei mir würden die Threads dann:
1/2# Threads: Empfangen + in Datenbank schreiben
1/2# Threads: Senden + aus Datenbank lesen

Gruß Frokuss

T
2.219 Beiträge seit 2008
vor 6 Jahren

@Frokuss
Deine Vorstellung von Facebook passt nicht ganz.
Facebook betreibt bei der Größe der Verbindungen, was täglich weit mehr als 1 Mrd. liegen dürfte, schon Serverfarmen und verteilt die Verbindungen dann innerhalb eines Clusters.

Eigentlich sollte bei einem sauberen Protokoll ein Thread sowohl schreiben als auch lesen können.
Zwei Threads für eine Verbindung sind selten sinnvoll.
Den auch der Client müsste dann über zwei Sockets arbeiten, damit er Daten senden und empfangen kann.
In der Regel hast du einen Client, der sich erst einmal beim Server anmeldet und danach Daten schickt.
Dann sollte der Client, wenn alle Daten vom Server empfangen und verarbeitet wurden, Antworten erhalten und irgendwann auch die Verbindung mit dem Server schließen.
Der Server sollte dann also entsprechend ein Protokoll haben, in dem ein Login, Datenpakete, Fehlerparket und Logout Paket vorhanden sind.

Somit kannst du dann deine Kommunikation auch ohne großen Aufwand auf UDP portieren, was bei vielen Verbindungen auch der bessere Ansatz ist.
Mit TCP hat man durch die Verbindungen, die gehalten werden müssen, dann auch irgendwann zu viele offene Verbindungen und somit zu viele laufene Threads.
Wenn diese dann auch noch imer Dauerfeuer Daten senden/empfangen, hat deine CPU irgendwann probleme die Daten noch rechtzeitig zu verarbeiten.

Hier solltest du also genau planen, was du erreichen willst und dir ein sauberes Protokoll samt Prüfsummen für die Übertragung überlegen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

W
872 Beiträge seit 2005
vor 6 Jahren

Schau Dir mal die Stackoverflow Architektur an. Das gibt Dir einen Einblick, wie so eine Architektur richtig aufgebaut ist. Facebook selber wird mit noch viel mehr Servern arbeiten.
Ich würde an Deiner Stelle mal gRPC, ZeroMQ, Aeron oder eine andere Middleware ausprobieren - das macht man, wenn man kein Google oder Facebook ist.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Danke für eure beiden Antworten.

Ich hoffe ich habe das jetzt irgendwie richtig verstanden:

1.) Warten bis eine TCP-Verbindung aufgebaut wird
2.) Auslagern in einen Thread
3.) Warten das Client sich Authentifiziert (mit Verarbeitung)
4.) Schlüssel/ID austauschen
5.) Thread mit TCP-Verbindung schließen

6.) Wie 1. und 2. nur in UDP
7.) Schlüssel/ID + Clientwunsch empfangen
8.) Verarbeiten und Client zurückschicken
9.) Wieder Thread mit Verbindung schließen

So in etwa richtig?

@weismat: Danke. Soweit wollte ich noch nicht raus - aber schön wäre es ja 😄 Nur wollte halt schon wissen/gucken wie es ist, wenn mal so etwas populär werden würde.

Gruß Frokuss

EDIT:
https://www.mycsharp.de/wbb2/thread.php?threadid=7803

Hallo zusammen,

Protokoll kann man ja auf unterschiedlichen Ebenen (des ISO-Schichtenmodells) verstehen. Mir ist gar nicht genau klar, welche Ebene gemeint ist. Wenn es sowas auf der Ebene http/ftp o.ä. gemeint ist, dürfte es nicht allzu schwierig werden. Syntax für Kommandos und Parameter definieren und als String hin- und herschicken.

herbivore Ich hoffe so war das mit dem Protokoll zu verstehen?

16.806 Beiträge seit 2008
vor 6 Jahren

Die Anzahl von Threads ist sekundär.
Mehr Threads sind nicht unbedingt schneller; oft ist das Gegenteil der Fall weil ein Thread auch immer 1 MB allokierter RAM bedeutet und der Overhead von Threads auch entsprechend Rechenleistung und Zeit benötigt.
Ein Thread für jeden Quatsch zu starten wird in 99,999% der Fälle das Gegenteil bringen: Performance und Skalierungsprobleme.

Viel wichtiger bei solchen Plattformen sind entsprechende Paradigmen und Architekturen wie zB. Microservices und CQRS.
Darüber wird skaliert; nicht mit Threads.

Das Vorgehen der Projektplanung finde ich zudem fahrlässig falsch.
Sich am Anfang um Low Level Aufgaben zu kümmern ist absolut nicht notwendig; dafür gibt es bereits entsprechende Webservice Technologien wie ASP.NET Core mit der WebAPI und Swagger für die Contracts.

Konzentrier Dich auf die Wertschöpfung und behandle andere Themen nur, wenn es unerlässlich ist.

T
2.219 Beiträge seit 2008
vor 6 Jahren

@Abt
Da der TE hier Networking mit C# lernen will, finde ich es als unerlässlich sich in die Basics mit TCP/UDP einzuarbeiten und dann auch mit Threads zu arbeiten, was ebenfalls ein Ziel des TE ist.
Ich würde sogar noch einen Schritt weitergehen, und so tief in die Materie herabsteigen und auf Sockets aufbauen.

Nur so kann man die Grundsätze im Detail verstehen.
Mit TCPListener etc. zu arbeiten, die den Socket kapseln, würde ich dann später arbeiten.

Auch würde ich für Lehrzwecke nicht auf Fertiglösungen wie eine Middleware aufbauen.
Für ein richtiges Projekt sind solche Lösungen dann aufjeden Fall vorzuziehen!
Aber gerade beim TE dürfte es primär erst einmal um Grundlagen und das Verständiss in der Netzwerkprogrammierung gehen.

@Frokuss
Mit Protokoll meine ich nicht http o.ä.
Hiermit ist der Aufbau deiner Daten, die du über das Netzwerk schicken willst gemeint.
Für ein einiges Protokoll brauchst du gewissen Grundsätze.
Du musst erst einmal die Übertragungsart(TCP/UDP) und den Port vorgeben.
Dann kommt der Datenaufbau.

Ein einfaches Paket sollte i.d.R. dann eine Magic Number als Start Markierung haben.
Dann sollten gewissen Paketinformationen wie ein Längen Byte, Pakettypen Byte, der Payload ein am Schluss eine Prüfsumme wie CRC16 dran hängen.
So hast du ein grobes Paketformat, mit dem dein Server und Client arbeiten können.
Für UDP soltest du dann sowohl auf Client als auch auf Server Seite ein ACK Paket haben, damit die Übertragung per UDP auch bestätigt wird.
Bei UP sollten die Clients auch mit dem Server eine eindeutige ID aushandeln, damit der Client vom Server bei jeder Anfrage erkannt wird.
Diese muss der Client dann auch im Paket mitliefern.

Hier solltest du dich mal in die entsprechende Lektüren einlesen und selbst testen und lernen.
Dies habe ich auch gemacht um mein Wissensstand aufzubauen um wiederum ein TCP/UDP Server umzusetzen.

Diesen benötige ich zur Betreuung von .Net 2.0 TCP/UDP Servern, die mit einigen hundert Clients arbeiten müssen.
Gerade wenn die Clients große Datenstände nachsenden müssen, muss pro Client dann ein Thread die Daten entgegennehmen, verarbeiten und die Verarbeitung bestätigen.

Hier hatte ich die Server mit Sockets umgesetzt, was an dieser Stelle mangels wissen über die TCP Klassen nicht anders umsetzbar war, da viele Tutorials immer direkt auf den Socket aufgebaut haben.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.806 Beiträge seit 2008
vor 6 Jahren

Für den Aufbau von Verständnis von Networking brauch ich aber nicht mit Kanonen auf Spatzen schießen. Das ist völlig kontraproduktiv, weil Du hier mehrere Baustellen gleichzeitig hast.
Da reicht auch eine Mini Chatapplikation.

T
2.219 Beiträge seit 2008
vor 6 Jahren

@Abt
Stimme ich dir zu.
Aber hier geht es beim TE eben um Grundlagen.
Was er genau beabsichtig mit dem Wissen zu machen, kann ich aktuell nicht wissen.
Aber auch hier muss der TE sich eben informieren und Grundlagen verstehen.
Was dann mit dem Wissen gemacht wird, ist dann wieder ein anderes Paar Schuhe für mich.

Interessant für den weiteren Verlauf wäre dann eben zu wissen, was das Programm genau machen soll.
Irgendwelche Daten schreiben/lesen ist sehr wage und sagt nichts wirklich aus.
Hier muss eben der Anwendungszweck bekannt sein um abschätzen zu können, ob Threads und co. in dieser Form überhaupt sinnvoll sind.
Viellleicht wäre sogar ein anderer Ansatz sinnvoller.
Hier darf aber eben nicht vermischt werden, dass es primär erst einmal um Grundlagen im Netzwerkbereich geht.
Bei einer spezifischen Umsetzung, sollte dann ein eigenen Thread geöffnet werden damit sich die Themen nicht vermischen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Vielen Dank für die Rückmeldungen 😃 Und sorry dass ich jetzt erst reagiere...

Ich verfolge natürlich hier gleich mehrere Ansätze... Zum einen will ich wie gesagt mich fortbilden und das ganze quasi auch direkt zweckgebunden anhand eines anschaulichen Projektes. Ob dieses Projekt am Ende was wird oder nicht, ist so gesehen unwichtig. Wenn es was wird, ist es gut, wenn nicht... wird es halt in den Müll gehauen.

Und in dem Projekt möchte ich quasi so etwas wie ein WhatsApp programmieren. Da ich schon realtiv viel mit dem ganzen Webzeugs gemacht habe, daher HTML, CSS, JS, PHP, MySQL und AJAX, weis ich ja im Grunde bereits was ich so an Daten brauchen. Das bedeutet ich kann mich natürlich nahezu komplett auf den Server konzentrieren. Die Clientanwendung kann ich so gesehen ruhig suboptimal sein... Daher ob ich da nun 3 oder 5 Threads verwenden würde ist erst einmal total egal.

Nun ist natürlich die Frage, warum ich Whatsapp nenne und nicht einen "Minichat". So einen Minichat kann man nämlich durch aus schlecht programmieren... WhatsApp hingegen hat viele Nutzer und das möchte ich quasi berücksichtigen. Also die Kombination aus Minichat und Whatsapp wäre wohl das richtige.

Ich habe mir am WE ein paar Gedanken dazu gemacht und mal überlegt wie man das theoretisch realisieren könnte. Da ja nicht gleichzeitig zu viele Threads offen sein dürfen, muss also die Anzahl irgendwie möglichst gering gehalten werden.

Dazu folgende Überlegung:
Jedesmal wenn der Server Daten an einen Client schicken muss, muss aus dem Hauptthread ein Thread zum senden der Daten erstellt werden, der nach dem senden wieder geschlossen wird (der Thread... nicht die Verbindung).
Beim Empfangen wäre es prima, wenn ich theoretisch eine Thread hätte, der alle Verbindungen auf inputs prüft und diese dann in eine geteilte Ressource (mit einem verarbeitendem Thread steckt). Dazu könnte man dann theoretisch eine List nehmen, bei dem der Empfangenen-Thread die Daten anhängt und der Verarbeitende-Thread immer das erste Element abarbeitet (also FIFO). Dazu habe ich aber noch kene Idee wie das gehen würde.
Werde mir aber mal folgendes dazu nachlesen, da dies sich interessant anhört und möglicherweise auch eine Lösung darstellt...:
https://www.computerbase.de/forum/showthread.php?t=730609 (insbesondere Post 7, der letzte)

So starte ich meinen Server:

//static void Main(string[] args)
IPEndPoint IPEP = new IPEndPoint(Dns.GetHostAddresses(host)[0], port);
listener = new TcpListener(IPEP);
listener.Start ();

//Damit lagere ich die Verbindungen in einen anderen Thread - damit mir die Console frei bleibt:
Thread th = new Thread(new ThreadStart(Run));
th.Start();

Die Run Methode:


while(isRunning){
  try{
	TcpClient c = listener.AcceptTcpClient();
	threads.Add(new ServerThread(c));//ServerThread hält aktuell die Verbindung immer offen und wäre sende bereit...
   }
   catch(Exception ex){
        isRunning = false;

        //Besonderer Fehler wenn der Thread geschlossen wird...
        if(System.Runtime.InteropServices.Marshal.GetLastWin32Error() != 10004)
        		Console.WriteLine(ex);
    }
}

Ich werde mich mal ein wenig in die genannten Dinge reinlesen. Danke euch!

Lieben Gruß Frokuss

T
2.219 Beiträge seit 2008
vor 6 Jahren

Ich habe den Verdacht, dass du hier in die falsche Richtung denkst.
WhatsApp und co. arbeiten nicht direkt mit TCP/UDP Servern.

Dort wird eher über eine API(Webservice) der ganze Prozess abgebildet.
Die Nachrichten werden vom Client dann abgefragt und auch vom Client an den Webservice gesendet.
Der Empfänger fragt dann wieder seine Nachrichten ab und sendet an andere Clients Nachrichten.

Dann bräuchtest du eher einen Server(PHP + MySQL) und deine Clients(Apps).
Dort sollten deine Clients über die API dann die Nachrichten sowohl senden als auch empfangen können.

Natürlich steht die auch die Möglichkeit offen es direkt über TCP/UDP zu lösen.
Dann musst du aber noch dein eigenes Datenformat(Protokoll) ausdenken samt Authentifizierung um auch die Daten an die richtigen sowie auch den erlaubten Clients zu senden.
Da du auch Nachrichten übertragen willst, solltest du dann eine Verschlüsselte Übertragung umsetzen.
Sonst könnten die Nachrichten abgefangen und ausgelesen werden.

Das Management der Verbindungen, solltest du dann auch in deinem Protokoll definieren.
Am besten wäre hier eher auf UDP aufzubauen.
Der Client muss sich dann anmelden, Daten senden/abfragen und wieder abmelden.
Hier würde ich aber auf UDP aufsetzen, TCP wäre hier nur sinnvoll wenn die Verbindungen auch gehalten werden sollen.
Zum kurzen Nachrichten senden/empfangen würde ich die Verbindungen nur kurz öffnen und dann schließen.

Nachtrag:
Dein aktueller Code sieht mir zu sehr nach Copy&Paste aus.
Hast du den Code selbst geschrieben oder nur irgendwo her kopiert?

Der Code aus deinem Link von Computerbase, taugt nur zu Testzwecken.
Deine Daten sollten dann besser bänar als Pakete übertragen werden.
Mit deinem eigenen Format machst du dies dann auch für andere Clients, also alternative Anwendungen, auch zugänglich.

Ebenfalls solltest du pro Verbindung einen Thread aufbauen.
Die Daten, die der Client sendet, sollten dem Server dann sagen was er tun soll.
Also brauchst du z.B. ein Byte Paket Typen mit dem der Server weiß, was er damit machen soll.
So kannst du ein Paket im Client bauen, dass dem Server sagt gib mir alle Nachrichten.
Oder eben auch, dass du ihm ein Nachrichten Paket gesendest hast für Client XY.
Somit brauchst du nur einen Thread pro Verbindung, kannst aber denoch deine Nachrichten senden und empfangen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

P
1.090 Beiträge seit 2011
vor 6 Jahren

Falls du einen Chat Programmieren willst denke ich ist SignalR eine gute Wahl. Einfach mal SignalR und Chat googlen, da findest du ein paar Beispiele.

Falls du Runter zu den Grundlagen willst, SignalR ist auch Open Source, und da solltest du eigentlich alles nötige finden.

UDP ist zwar deutlich schneller als TCP. Hat aber da Problem, das nicht garantiert wird das alle Pakete ankommen und auch nicht in der Richtigen Reihenfolge. Was bei Textnachrichten unschön sein kann.

TCP sorgt dafür das alle Pakete ankommen und auch in der Richtigen reihen folge. Dafür ist es langsame, was bei Textnachrichten aber meist nicht so wichtig ist. Bei Voice over Ip ist es schon störender, da stört es dann aber nicht wenn mal ein Paket weg fällt.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

@T-Virus: Ich glaube ich weis, warum du glaubst dass das nach Guttenberg ist... Weil in dem einem Catch der Fehler 10004 abgefangen wird.
Tatsächlich habe ich versucht den Server über Console wieder zu beenden. Dabei ist dann immer eine Absturzmeldung mit dem 10004 Code gekommen. Also habe ich danach gegoogelt... und auch was gefunden... Ist jetzt leider schon ein paar Tage her... Deswegen weis ich nicht mehr den genauen Sinn der Aussage.
Irgendwas mit es wird kein Rückgabewert geliefert beim Stoppen und deswegen kommt dieser Fehlercode... Also so gesehen was harmloses. Ich habe es also dementsprechend bewusst selber eingefügt und war vor dem google nicht im Stande dies zu beheben.

Grundsätzlich kopiere ich nichts blind. Das macht überhaupt keinen Sinn. Ich halte mich dann aber schon um Grunde an Anleitungen oder Tutorials und versuche diese dann in meinem Sinne zu vervollständigen.

Der gepostete Thread (von mir) hat mich auf eine gute Idee gebracht. Diese versuche ich grade umzusetzen - ob das klappt weis ich noch nicht - würde es aber dann entsprechend mitteilen.

@Palin: Im groben kenne ich den Unterscheid zwischen TCP und UDP. Daher würde ich bei Text schon noch TCP verwenden - würde ich aber sowas wie TeamSpeak versuchen zu implementieren, würde ich UDP nehmen.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Sooo,
ich habe nun eine Server, quasi aus 3 Threads besteht, die permanent am laufen sind.
1.) Der Programmstart (ist das überhaupt ein Thread?)
2.) Nach dem Start ein Thread der der Verbindungen entgegennimmt
3.) Ein Thread, der auf StreamRead reagiert und dann das in seperaten Threads "verarbeitet"

Hier einmal mein Code (nur der Server):


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Test_TCP{
	
	class Program
	{
		const string host = "localhost";
		const int port = 8888;
		const int hauptThreadAnzahl = 2;
		
		private static TcpListener listener = null;
		private static List<TcpClient> clientConnections = new List<TcpClient>();
		private static bool isRunning = true;

        public static void Main(string[] args){
			int hauptThreadCounter = 0;
        	IPEndPoint IPEP = new IPEndPoint(Dns.GetHostAddresses(host)[0] ,port);
			listener = new TcpListener(IPEP);
			listener.Start ();
			
			//Thread: Verbindungen annehmen (läuft 24/7)
			Thread th1 = new Thread(new ThreadStart(Run));
			th1.Start();
			Console.WriteLine("Thread: Verbindungen gestartet (" + (++hauptThreadCounter).ToString() + "/" + hauptThreadAnzahl.ToString() + ")");
			
			//Thread: StreamReader für alle Verbindungen (läuft 24/7)
			T_ClientReader th2 = new T_ClientReader(ref clientConnections);
			Console.WriteLine("Thread: Reader gestartet (" + (++hauptThreadCounter).ToString() + "/" + hauptThreadAnzahl.ToString() + ")");
			
			//Thread: StreamWriter (läuft nur nach Bedarf)
			//To Do
			
			String cmd = "";
			
			while(!cmd.ToLower().Equals("stopp")){
				Console.WriteLine("");
				Console.WriteLine("Warte auf Admineingaben:");
				cmd = Console.ReadLine();
				
				if(!cmd.ToLower().Equals("stopp")){
					Console.WriteLine("Unbekannter Befehl: " + cmd);
					Thread.Sleep(1250);
				}
			}
			
			Console.WriteLine("Thread: Reader wird geschlossen...");
			th2.StoppeThread();
			Console.WriteLine("geschlossen");
			Console.WriteLine("");
			Console.WriteLine("Thread: Verbindungen wird geschlossen...");
			StoppeThread();
			Console.WriteLine("geschlossen");
			Console.WriteLine("");
			
			Console.WriteLine("Fenster schliesen... (ENTER)");
			Console.ReadLine();
        }
        
		public static void Run(){
			while(isRunning){
        		try{
					TcpClient c = listener.AcceptTcpClient();
					clientConnections.Add(c);
        		}
        		catch(Exception ex){
        			isRunning = false;
        			
        			if(System.Runtime.InteropServices.Marshal.GetLastWin32Error() != 10004)
        				Console.WriteLine(ex);
        		}
				
				Thread.Sleep(100);
			}
		}
		
		public static void StoppeThread(){
			try{
				listener.Stop();
			}
			catch(Exception ex){
				isRunning = false;
				Console.WriteLine(ex);
			}
			
			isRunning = false;
		}
    }
	
	class T_ClientReader{
		private bool stop;
		private List<TcpClient> alleClients;
		
		public T_ClientReader(ref List<TcpClient> verbindungen){
			stop = false;
			alleClients = verbindungen;
			
			new Thread(new ThreadStart(Run)).Start();
		}
		
		public void Run(){
			int i;
			NetworkStream ns;
			
			while(!stop){
				for(i=0; i<alleClients.Count; i++){
					ns = alleClients[i].GetStream();
					
					if(ns.DataAvailable){
						//Hier einen neuen Thread starten und den Reader erstellen...
						OpenReader(ns);
						//TODO
						//Nach dem Empfangen und Verarbeiten muss der Thread direkt wieder geschlossen werden!
						//Eventuell mit einer Empfangbestätigung abrunden...
					}
					
				}
				Thread.Sleep(100);
			}
		}
		
		public void OpenReader(NetworkStream ns){
			StreamReader lesen = new StreamReader(ns);
			Byte[] sendBytes = Encoding.ASCII.GetBytes("Empfangen :-)\r\n");
			
			Console.WriteLine("TempReader-Thread geöffnet...");
			Console.WriteLine(lesen.ReadLine());
			ns.Write(sendBytes, 0, sendBytes.Length);
			Console.WriteLine("TempnReader-Thread geschlossen...");
		}
		
		public void StoppeThread(){
			stop = true;
		}
	}
}

Im Grunde muss ich mich nun darum kümmern, was wie wo am besten geeignet ist für die Kommunikation. Daher:
1.) reichen nun die Stream-Methoden? Welche Alternativen gibt es
2.) wie sieht eine gescheite Verschlüsselung aus?
3.) wie gestallte ich das Protokoll? -->

[...]

Ein einfaches Paket sollte i.d.R. dann eine :::

Da muss ich mich erst richtig reinarbeiten... Da sind leider noch zu viele Fragezeichen bei mir...

Trotzdem habe ich noch eine Frage zu meinem eigenen Code O.o

1.) Macht es überhaupt Sinn, dass ich nun alles auf diese drei Threads laufen lasse?
2.) Ist das überhaupt praktikabel? Oder würde mir dabei der Server in die Knie gehen?
3.) Kann das gegenwärtig zu Problemen führen mit der Referenz (ist ja immerhin eine geteilte Ressource (Klasse T_ClientReader), was theoretisch zu einem Deadlock führen könnte... oder?)

Ich hoffe ihr könnt mir darauf ein paar Antworten geben 😃 Wie gesagt... mein TODO ist noch groß...

Besten Gruß
Frokuss

T
2.219 Beiträge seit 2008
vor 6 Jahren

@Frokuss
Dein Ansatz mit drei Threads ist nicht sinnvoll.
Besser wäre ein Thread um die Verbindungen entgegen zunehmen.
Dann machst du nach einem Verbindungsaufbau einen Thread nur für diese Verbindung.
So nimmt dein Server die Verbindungen entgegen und schiebt diese einfach in einen eigenen Thread.

Wenn du mit den Sockets arbeiten würdest, könntest du einfach warten bis ein Socket sich mit deinem Server Socket verbindet und den Client Socket dann an deine Thread Methode als Parameter Objekt mitgeben.

Du hast dann also einen Server Thread und pro Verbindung einen eigenen Thread.
Mehr Threads brauchst du erst einmal nicht.

Jeder Thread sollte dann auch seine eigenen Resourcen haben und keine Resourcen teilen soweit möglich.
Du musst dich hier auch noch mit dem Thread Locking auseinander setzen.
Dies wird früher oder später auch ein Problem werden, damit eben bestimmte Resourcen, die du nicht teilen kannst, gegen parallele Verarbeitungen absicherst.

Deine Threads für StreamReader/StreamWriter kannst du dir sparen.
Der Thread für die Verbindungen muss sich um das Senden/Empfangen von Daten kümmern.
Dies sollte nie in der Verantwortung eines Threads liegen, der global gültig ist.
Hier baust du dir sonst selbst eine Bremse ein, wenn du deine Clients über einen Sende- und einen Empfangsthread laufen lassen willst.

Jede Verbindung sollte in ihrem Thread die Nachrichten senden und empfangen.
Wie gesagt, bau dir dafür ein Protokoll.
Ansonsten wäre der Ansatz von Palin mit SignalR fast schon die Ideal Lösung.
Dort hast du deine schon einen "fertigen" Client/Server und kannst dich direkt auf die Kommunikation kümmern.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Guten Morgen 😃

Danke! Also ich habe mir das mal Thread locking angeguckt und ist so gesehen erst einmal recht einfach zu integrieren... Habe ich das richtig erkannt, dass so gesehen dieser lock-Block nicht unterbrochen werden kann und somit immer bis zum Ende durchgeführt wird?

Macht das nur Sinn wenn man geteilte Ressourcen hat, oder sollte man sowas immer verwenden?

So habe ich zB folgenden Aufruf:

//List<Sockel> anwenderGeraet;
//Dictornary<int,Sockel> anwenderID
threadLink = new AnwederAuth(ref anwenderGeraet, ref anwenderID);

AnwenderAuth wäre bei mir ein Thread, der die Anwender Authentifizierung abwickelt und dann nach erfolgreicher Abwicklung den Socket aus der Liste in den Dictonary "verschiebt". Wenn ich dass so nun richtig verstanden habe, wäre dies ein kritischer Bereich?

						lock(anwenderGeraet){
							if(true){//TODO richtiges Prüfen mit Datenbank...
								int uID = new Random().Next(0,100);
								Byte[] senden = Encoding.ASCII.GetBytes(Statements.Prepare("login", "ok")+"\r\n");
								
								//Neuen AnwenderThread erstellen...
								if(anwenderID.ContainsKey(uID)){//bereits vorhanden --> Hinzufügen
									//TODO Geräte Auto-Login
									anwenderID[uID].Add(new Random().Next(0,20), anwenderGeraet[i]);
								}
								else{//TODO Geräte ID
									anwenderID.Add(uID, new AnwenderThread(uID, new Random().Next(1,20), anwenderGeraet[i]));
								}
								anwenderGeraet.Remove(anwenderGeraet[i]);
								
								break;//Schliefe wieder von vorne beginnen lassen...
							}
						}

Wobei das ganze ja scheinbar erst funktioniert, wenn ich einen weiteren thread habe, mit dem gleichen Objekt im lock?

btw. Habe nun von TcpClient auf Socket umgestellt. Habe aber für mich da jetzt keinen Vorteil gesehen und auch keine großen Veränderungen. Ich habe dazu nun auch deinen Rat beherzig und jedem Anwender einen eigenem Thread gegeben (unter einigen vorbehalten). Ansonsten habe ich die Threads dahingehend angepasst, dass ich nun einen Thread habe, der die Verbindungen engegegen nimmt, einen Thread der eine Anwender authentifizierung übernimmt und dann erst einen persönlichen Thread erstellt. Das mit der Authentifizierung verwende ich, da theoretisch jeder Anwender mehrere Geräte haben könnte...

Lieben Gruß
Frokuss

T
2.219 Beiträge seit 2008
vor 6 Jahren

@Frokuss
Freut mich, dass du dich in das Thema eingelesen hast.
Thread locking solltest du nur dort verwenden, wo es nötig ist.
Dies soll nur verhindern, dass die Threads parallel in eine Resources z.B. schreiben und es dann knallt.

Deinen Bsp. Code soltlest du dabei noch optimieren.
Es wäre schlecht, wenn sich in deinem Block wegen langer DB Abfragen die Threads durch das Locking einreihen.
Dies würde deine Anwendung wieder ausbremsen.
Ebenfalls solltest du nicht immer wieder ein neues Random Objekt anlegen.
Diese haben bei dir auch kein Seed und nutzen somit per Default die Startzeit deines Rechners und kommen somit zu gleichen Ergebnissen.
Hier solltest du z.B. von der aktuellen Zeit die Millisekunden o.ä. nehmen um ein relativ eindeutiges Seed zu haben und somit auch unterschiedliche Ergebnisse.

Besser wäre es aber, wenn du in Random Objekt hast, was du wieder verwendest.
Dies spart dir dann die aufwändige Objekt Generierung bei Random.

Einen Lock Block kann man natürlich verlassen.
Hier müsste der entsprechende Code aber in einer eigenen Methode sein aus der du dann einfach raus springst.
Besser wäre esa ber vor dem Lock Block eine if Abfrage zu machen, damit du nicht immer in den Block springen musst.
Ebenfalls sollte der Block kurz sein, also schnell durchlaufen werden können ohne zu lange auf irgendwas warten zu müssen.

In deinem Fall sollte also die if Abfrage vor das Lock, damit du deine Threads nicht blockst.
Was dein break bezwecken soll, ist mir aber nicht klar.
break beendet eigentlich die Schleife, continue setzt diese fort.

Ebenfalls solltest du mal schauen, dass du bei Zeiten deinen Stand per Unit Test mal durchlaufen lässt.
Dabei kannst du zum einen sicherstellen, dass dein Code funktioniert.
Ebenfalls solltest du mit Test Clients einen Last Test fahren um zu prüfen wie schnell und wie viele Clients von deinem Server bearbeitet werden können.
Auf Server Seite solltest du dann entsprechend per Config ein Logging einbauen um zu schauen wo deine Verarbeitung pro Client am längsten braucht.
Somit kannst du auch deinen Server optimieren um ihn auf bei vielen Clients nicht zu sehr zu überlasten.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Thread locking solltest du nur dort verwenden, wo es nötig ist.
Dies soll nur verhindern, dass die Threads parallel in eine Resources z.B. schreiben und es dann knallt.

Und genau dazu habe ich noch eine Frage... Ich habe jetzt nur diesen einen lock eingebaut! Ist das dann diese Ressource auch in anderen Threads abgedeckt? Daher wenn ich in einem anderen Thread grade an der Ressource rumfummel, kann dieser Thread nicht drauf zu greifen? Bzw. auch andersherum, daher wenn dieser Thread im Lock-Block ist, können die anderen Threads nicht darauf zugreifen?

Diese Random Zahl, soll nur was mit der Datenbank skizzieren... und mir einfach eine "User ID" liefern. Daher nur für Testzwecke. Wenn ich dass mit dem Sockets und Threads ordentlich verstanden habe, dann werde ich wohl MySQL einbinden.
Danke für den Tipp mit der DB außerhalb des Lock-Blockes. Ich hätte wohl die Abfrage mit reingepackt O.o
Das break ist für eine Schleife die noch um den Block herum liegt. ich könnte so mehrere "einloggen" nacheinander abarbeiten. Wobei die Schleife nur abgebrochen wird, wenn sich einer erfolgreich einloggt, da dann ein Listenelement entfernt wird und dies unter Umständen zu Indiziesveränderungen kommt.

Zufallszahlen werden ich nur nehmen um einen Saltvalue für das Hashen von Passwörtern zu bekommen.

Lieben Gruß
Frokuss

Nachtrag: Wie mache ich denn so einen Stand / Unit - Test am besten? Einfach google verwenden?

16.806 Beiträge seit 2008
vor 6 Jahren

Zunächst mal bitte die Forenregeln beachten, das heisst eine Frage pro Thread und vorher mal die Forensuche nutzen.

[Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio

Ebenso mal die Basics von Threads und Lock anschauen.
[Artikel] Multi-Threaded Programmierung
Locken muss man überall aus jedem Thread, sonst macht es keinen Sinn.

Locks kann man aber relativ oft durch eine bessere Architektur vermeiden.

Verrenn dich nicht, weil Du an zu vielen Baustellen gleichzeitig arbeitest.
Mach lieber erstmal ein Thema durch...

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Eigentlich hast du recht Abt 😄

Also. Deswegen poste ich jetzt einfach mal meinen Code (nur Server), der bereits etwas über das rein technische hinausgeht. Gegenwärtig habe ich nur eine Stelle mit dem Lock drinnen und muss wissentlich auf jeden Fall an einer weiteren Stelle integriert werden!

Leider sind dies mittlerweile mehr als 300 Zeilen Code, weshalb ich einfach mal etwas dazu sage. **:::

Also:
1.) Das Programm erstellt einen Thread (Verbindungen)
2.) Verbindungen erstellt einen weiteren Thread (AnwenderAuth)
3.) Ist die Authentifiziierung erfolgreich, wird jeweils ein Thread (AnwenderThread) pro Anwender erstellt (Hinweis: hier können mehrere Clients einem Anwender zugewiesen werden)
4.) Die Klasse Statements kommt **vermutlich **einem Protokoll recht nahe

  • 4 Bytes (num.): Befehl
  • 4 Bytes (num.): Text länge
  • x Bytes (string): Text
    Anmerkung: Natürlich müsste am besten das ganze Paket verschlüsselt werden.
    5.) Es fehlt natürlich eine Datenbank anbindung (gegenwärtig). Deswegen ist das Authentifizierungspasswort manuell mit "Hallo1234" angegeben.
    6.) Ein wenig sehr sparsam mit Kommentaren... sorry
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using MySql.Data.MySqlClient;

namespace Server_Sockets
{
	class Program{
		public static Verbindungen t1;
		public static bool beenden = false;
		
		public static void Main(string[] args){
			Verbindungen t1 = new Verbindungen();
			
			string cmd = "";
			Console.WriteLine("Gib einen Befehl ein:");
			while(!beenden){
				cmd = Console.ReadLine();
				Console.WriteLine();
				CmdDo(cmd);
			}
		}
		
		private static void CmdDo(string cmd){
			if(cmd.ToLower().Equals("stopp"))
				CmdStopp();
			else if(cmd.ToLower().Equals("hilfe"))
				CmdHelp();
		}
		
		private static void CmdHelp(){
			Console.WriteLine("stopp\t\tbeendet den Server komplett");
			Console.WriteLine("hilfe\t\tzeigt Infos über alle Befehle");
			
			Console.WriteLine();
			Console.WriteLine("Gib einen Befehl ein:");
		}
		
		private static void CmdStopp(){
			//t1.threadLink.Beenden();
			Console.WriteLine("muss noch irgendwie gemacht werden... komische Exeptions abfangen!");
			beenden = true;
		}
	}
	
	class Verbindungen : ThreadControll{
		const string host = "localhost";
		const int port = 8888;
		
		IPEndPoint ep;
		Socket lauscher;
		List<Socket> anwenderGeraet;//Verbundene Geräte (1 User : N Geräte) <-- abarbeiten
		Dictionary<int, AnwenderThread> anwenderID;//Alle Geräte gebündelt in einem Thread
		
		public AnwederAuth threadLink;
		
		public Verbindungen(){
			
			ep = new IPEndPoint(Dns.GetHostAddresses(host)[0], port);
			lauscher = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
			anwenderGeraet = new List<Socket>();
			anwenderID = new Dictionary<int, AnwenderThread>();
			
			thread = new Thread(new ThreadStart(Starten));
			thread.Start();
			
			threadLink = new AnwederAuth(ref anwenderGeraet, ref anwenderID);
		}
		
		public void Starten(){
			Console.WriteLine();
			Console.WriteLine("Verbindungs-Thread gestartet...");
			try{
				lauscher.Bind(ep);
				lauscher.Listen(100);
				
				while(!beenden){
					anwenderGeraet.Add(lauscher.Accept());
					
					Thread.Sleep(50);
				}
			}
			catch(Exception ex){
				Console.WriteLine("Fehler: " + ex);
			}
			
			Console.WriteLine("Verbindungs-Thread beendet...");
			Console.WriteLine();
		}
	}
	
	class AnwederAuth : ThreadControll{
		List<Socket> anwenderGeraet;//Verbundene Geräte (1 User : N Geräte) <-- abarbeiten
		Dictionary<int, AnwenderThread> anwenderID;//Alle Geräte gebündelt in einem Thread
		
		public AnwederAuth(ref List<Socket>ag, ref Dictionary<int,AnwenderThread> ai){
			anwenderGeraet = ag;
			anwenderID = ai;
			
			thread = new Thread(new ThreadStart(Starten));
			thread.Start();
		}
		
		public void Starten(){
			int i;
			NetworkStream ns;
			StreamReader daten;
			string info;
			string[] cmdArr;
			string db_pw;
			
			Console.WriteLine();
			Console.WriteLine("Authentifizierungs-Thread gestartet...");
			
			while(!beenden){
				for(i=0; i<anwenderGeraet.Count; i++){
					ns = new NetworkStream(anwenderGeraet[i]);
					if(ns.DataAvailable){//Login verarbeiten
						daten = new StreamReader(ns);
						
						info = daten.ReadLine();
						cmdArr = Statements.GetClientCMD(info);
						
						Console.WriteLine(Statements.HelpCMD(cmdArr[0]));
						
						db_pw = "Hallo1234";//TODO richtiges Prüfen mit Datenbank...
						int uID = 1, gID = 2;//kommt aus der DB
						
						if((cmdArr[0].Equals("0010") || cmdArr[0].Equals("0011")) && cmdArr[2].Equals(db_pw)){
							
							lock(anwenderGeraet){
								Byte[] senden = Encoding.ASCII.GetBytes(Statements.Prepare(Statements.CMD.loginOk, uID.ToString()+"#"+gID.ToString())+"\r\n");
								ns.Write(senden, 0, senden.Length);
								
								//Neuen AnwenderThread erstellen...
								if(anwenderID.ContainsKey(uID)){//bereits vorhanden --> Hinzufügen
									//TODO Geräte Auto-Login
									anwenderID[uID].Add(new Random().Next(0,20), anwenderGeraet[i]);
								}
								else{//TODO Geräte ID
									anwenderID.Add(uID, new AnwenderThread(uID, new Random().Next(1,20), anwenderGeraet[i]));
								}
								anwenderGeraet.Remove(anwenderGeraet[i]);
								
								break;//Schliefe wieder von vorne beginnen lassen...
							}
						}
						else{
							Byte[] senden = Encoding.ASCII.GetBytes(Statements.Prepare(Statements.CMD.loginErr, "")+"\r\n");
							ns.Write(senden, 0, senden.Length);
						}
					}
				}
				Thread.Sleep(50);
			}
			Console.WriteLine("Authentifizierungs-Thread beendet...");
			Console.WriteLine();
		}
	}
	
	class AnwenderThread : ThreadControll{
		private Dictionary<int,Socket> geraete;
		private NetworkStream ns;
		
		public int userID;
		public string nick, vorname, nachname;//TODO
		
		public AnwenderThread(int uID){
			geraete = new Dictionary<int, Socket>();
			userID = uID;
		}
		
		public AnwenderThread(int uID, int gID, Socket con) : this(uID){
			geraete.Add(gID, con);

			thread = new Thread(new ThreadStart(Starten));
			thread.Start();
		}
		
		public void Starten(){
			Console.WriteLine();
			Console.WriteLine("Anwender-Thread ("+userID.ToString()+") gestartet...");
			
			string[] cmdArr;
			
			while(!beenden){

				foreach(Socket device in geraete.Values){
					ns = new NetworkStream(device);
					if(ns.DataAvailable){//Clientdaten verarbeiten
						cmdArr = Statements.GetClientCMD(new StreamReader(ns).ReadLine());
						
						if(cmdArr[0].Equals("0020")){
							Byte[] senden = Encoding.ASCII.GetBytes(Synchronisieren("0815 Datum")+"\r\n");
							ns.Write(senden, 0, senden.Length);
						}
					}
				}
				
				if(geraete.Count == 0)
					beenden = true;
				
				Thread.Sleep(50);
			}
			Console.WriteLine("Anwender-Thread ("+userID.ToString()+") beendet...");
			Console.WriteLine();
		}
		
		public void Add(int gID, Socket con){
			//Bestehende Verbindungen beenden (bei doppeltem Login)
			if(geraete.ContainsKey(gID)){
				geraete[gID].Disconnect(true);
				geraete.Remove(gID);
			}
			
			geraete.Add(gID, con);
			
			if(beenden){//Thread wieder starten
				beenden = false;
				thread = new Thread(new ThreadStart(Starten));
				thread.Start();
			}
		}
		
		private string Synchronisieren(string date){//TODO
			return Statements.Prepare(Statements.CMD.synchFreunde, "0815Freund");
		}
	}
	
	class Statements{
		public enum CMD {loginOk, loginErr, synchFreunde, synchGruppen, synchMsg, synchDate};
		public static string HelpCMD(string cmd){
			if(cmd.Length < 4)
				return "Fehler: Code zu klein ("+cmd.Length+"/4)";
			else if(cmd.Length > 4)
				return "Fehler:Code zu groß ("+cmd.Length+"/4)";
			
			//TODO :Befehl-Codes
			else if(cmd.Equals("0000"))
				return "update";
			else if(cmd.Equals("0001"))
				return "Client disconnect";
			else if(cmd.Equals("0010"))
				return "Manuelles einloggen";
			else if(cmd.Equals("0011"))
				return "Automatisches einloggen";
			else if(cmd.Equals("0020"))
				return "Synchronisieren";
			
			//TODO :Fehler-Codes
			else if(cmd.Equals("9998"))
			    return "Client-Code/String hat abweichende länge";
			else if(cmd.Equals("9999"))
				return "Client-Code/String ist kürzer als Mindestlänge";
			        
			else
				return "Code nicht bekannt";
		}
		
		public static string[] GetClientCMD(string cmd){
			if(cmd.Length < 9)
				return new string[]{"9999"};
			
			string pre, l, post;
			pre = cmd.Substring(0,4);
			l = cmd.Substring(4,4);
			
			while(l.Substring(0,1).Equals("0") && l.Length > 0)
				l = l.Substring(1, l.Length -1);
			
			//Fehler:
			if(cmd.Length != int.Parse(l) + 8)
				return new string[]{"9998"};
			
			if(l.Length == 0)
				l = "0";
			
			post = cmd.Substring(8, int.Parse(l));
			
			return new string[]{pre, l, post};
		}
		
		public static string Prepare(CMD cmd, string info){
			string pre = "", post = "", preL = "";
			int laenge;
			if(cmd.Equals(CMD.loginOk)){
				pre = "0010";//login - okay
				post = info;
			}
			else if(cmd.Equals(CMD.loginErr)){
					pre = "0011";//login - fehlgeschlagen
					post = "";
			}
			else if(cmd.Equals(CMD.synchFreunde)){
				pre = "0020";
				post = info;
			}
			else if(cmd.Equals(CMD.synchGruppen)){
				pre = "0021";
				post = info;
			}
			else if(cmd.Equals(CMD.synchDate)){
				pre = "0022";
				post = info;
			}
			else if(cmd.Equals(CMD.synchMsg)){
				pre = "0023";
				post = info;
			}

			preL = post.Length.ToString();
			while(preL.Length < 4)
				preL = "0" + preL;

			return pre + preL + post;
		}
	}
	
	class ThreadControll{
		public bool beenden;
		public Thread thread;
		
		public ThreadControll(){
			beenden = false;
		}
		
		public void Beenden(){
			beenden = true;
		}
	}
}

Gruß Frokuss

16.806 Beiträge seit 2008
vor 6 Jahren

Uh, da haben wir aber noch ganz schön viele Basics aufzuarbeiten, zB. das korrekte Abräumen von Ressourcen wie Streams mit Hilfe von Dispose().

Ich geb Dir echt den Tipp: geh viel abstrakter vor.

Geht erst mal das Thema durch und lass zwei Applikationen miteinander kummizieren, ohne sich um Sockets oder Threads kümmern zu müssen.
Lerne, wie man OOP programmiert und wie man die Verantwortlichkeiten durch Architektur trennt.

Und dann geht mit der Zeit immer tiefer und ersetze dann Teile der Kommunikation durch einene zB. Socket-Module.
So aber... viel zu viel auf einmal.
Das hier läuft darauf hinaus, dass Du das zwar alles mal gesehen hast, aber nichts davon so wirklich verstanden hast und damit für die Zukunft auch nicht stabil und sicher anwenden kannst.
Das ist ja nicht das große Ziel des Lernens.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Danke für die Hinweise 😃

Aktuelle habe ich überall wo es sinnvoll war das Dispose/Close eingefügt und auch meinen Quellcode stark umstrukturiert. Dabei habe ich mich mehr mit static und protektet auseinander gesetzt.

Gruß Frokuss

16.806 Beiträge seit 2008
vor 6 Jahren

static sollte man vermeiden, wenn man kann.
Code, der als static markiert ist, kann nicht gemockt werden, sodass dieser Gesamheitlich nicht oder nur mit sehr hohem Aufwand automatisch getestet werden kann.
[Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 6 Jahren

Auch wenn ich länger hier nicht mehr geschrieben habe... Erst einmal vielen Dank für die zahlreichen Hilfestellungen!

An dieser Stelle möchte ich nur noch eventuellen Amateuren wie mir einen nützlichen Hinweis mitgeben, der in dem Fall sich auf das Streamen bezieht:

Baut auf jeden Fall immer einen Thread.Sleep() zwischen jedem Stream.WriteLine() ein. Das ganze lokal getestet mag eventuell funktionieren - aber ich habe nun mehrere Stunden damit verbracht herauszufinden, warum ein anderes System (sehr langsam und über Netzwerk) nicht alle Daten empfängt, was genau an dem fehlenden Thread.Sleep lag.

Lieben Gruß
Frokuss

PS: Der Thread ist für mich nun erst einmal geschlossen!
PSS: @Abt static kann aber durchaus sinnvoll sein. Wenn ich mich nicht Irre (was sehr gut sein kann!), ist dies ja auch bei vielen Math.xxx Methoden der Fall?

2.298 Beiträge seit 2010
vor 6 Jahren

Hallo,

Static-Klassen und Methoden machen am meisten da Sinn, wo man kein physisches Objekt hat dem die Funktionalitäten zuzordnen sind. Daher ist dies eben bei der Math-Klasse entsprechend implementiert.

Überall da, wo sachen direkt einem Konkreten Objekt zuzuordnen sind, macht Static aber eben im Sinne von OOP keinen Sinn.

Gerade in deinem Fall hat Static an so gut wie keiner Stelle etwas zu suchen.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

T
2.219 Beiträge seit 2008
vor 6 Jahren

@Frokuss
Zusätzlich zu den Infos von inflames2k, halte ich deinen Ansatz mit Thread.Sleep auch für falsch.
Dies ist ein deutlicher Hinweis, dass du ihr wieder mit static arbeitest.
Gerade bei Multithreading Anwendungen ist static der falsche Ansatz.
Jeder Thread sollte seine eigenen StreamReader/Writer haben und möglichst keine oder nur wenige Resourcen teilen.
Sonst bremst du dich selbst nur aus bzw. musst Performance wegen Thread Locking opfern.

Je nachdem wie dein aktueller Code aussieht, solltest du deine Architektur überdenken um nicht auf solche Mittel zurück greifen zu müssen.
Gerade bei parallelen Anwendungen muss jeder Thread ohne Locking oder Hacks mit Thread.Sleep auskommen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

R
74 Beiträge seit 2006
vor 6 Jahren

Für jede Verbindung ein neuer Thread ist nicht wirklich optimal

https://www.youtube.com/watch?v=U5bTvc4yv5U&t=5514s

Die Anzahl möglicher Threads pro Prozess ist begrenzt - und die Grenze liegt nicht bei 10.000

Die Anzahl erzeugter Threads werden besser überwacht und begrenzt.

Asynchronous Socket Programming in C#

Threading

Viel Spaß und Erfolg

D
985 Beiträge seit 2014
vor 6 Jahren

Hmmm, hat hier irgendwer behauptet, für jede Verbindung soll ein neuer Thread erzeugt werden?

Die Aussage war/ist: Jeder Thread braucht seine eigene Verbindung.

Und das ist ein himmelweiter Unterschied.

R
74 Beiträge seit 2006
vor 6 Jahren

sorry hab es wohl falsch verstanden.

"Dabei wird jeder TcpCient in einen eigenen Thread ausgelagert und sendet (Stream.Write) in endlosschleife Daten an den jeweiligen Client."

"Ich hoffe ich habe das jetzt irgendwie richtig verstanden:

1.) Warten bis eine TCP-Verbindung aufgebaut wird
2.) Auslagern in einen Thread"

"

while(isRunning){
   try{
     TcpClient c = listener.AcceptTcpClient();
     threads.Add(new ServerThread(c));//ServerThread hält aktuell die Verbindung immer offen und wäre sende bereit...
    }
    catch(Exception ex){
         isRunning = false;

         //Besonderer Fehler wenn der Thread geschlossen wird...
         if(System.Runtime.InteropServices.Marshal.GetLastWin32Error() != 10004)
                 Console.WriteLine(ex);
     }
 } 

"

und so geht es durch den ganzen Thread.

Es sollte dennoch möglich sein ohne Blockierungen mehrere Verbindungen
in einem einzigen Thread verarbeiten zu können. Deswegen der Link zu
Asynchronous IO.

jetzt hab ich auch das Beispiel aus MSDN gefunden:
https://msdn.microsoft.com/de-de/library/fx6588te(v=vs.110).aspx

Die Asynchronous IO per Netzwerkkarte erfordert keinen Thread in der Anwendung auch das Betriebssystem muss keinen Thread zwangsläufig erstellen. Wie im oben verlinkten Video am Bsp. der Festplatte erläutert wird.