Ich bin dabei einen TCP-Server in C++ für Linux zuschreiben. Dieser soll mehrere Clienten gleichzeitig behandeln können, also hab ich mal versucht für jeden Client ein Thread zu spawnen, allerdings ist der RAM Verbrauch ziemlich schnell gestiegen, deswegen hab ich diese Idee wieder verworfen. Mein 2ter Ansatz war Threads und select() zu kombinieren, allerdings stoße ich dabei auf das Problem, dass ich keine Ahnung von der Funktionsweise von select() hab, ich hab anhand von diesem http://www.lowtek.com/sockets/select.html Tutorial probiert das einzubauen, aber ich blicke einfach nicht durch was man in welcher Reihenfolge machen muss, was relevant ist und was irrelevant ist. Außerdem ist diese Kombination überhaupt sinnvoll oder gibt es auch einfachere Methoden einen Multi TCP Server zu erstellen ? Bzw. Kennt ihr vllt bessere Beispiele oder schon einfache fertige TCP Serverklassen(Einfach im Sinne von wirklich nur TCP Serverklasse und nicht x andere integrierte Dinge)?
Es gibt mehrere Möglichkeiten; welche die sinnvollste ist hängt von dem jeweiligen Fall ab. Bevor hier wieder Tausend Varianten diskutiert werden, die dann zu 99% eh nicht auf deinen Fall passen, wäre es sinnvoll mal zu sagen, worum es genau geht. Vor allem wieviele Clients (ich mag nicht glauben, daß du von ein paar Clients einen spürbaren RAM-Verbrauch merkst), und ob die länger bedient werden sollten oder nur kurz, ob das Ganze dann mehrere Kerne wirklich nutzen muß etc.
Abgesehen davon ist select nicht so schwer zu verstehen: - alle fds, von denen gelesen werden soll (also bei einem Server den listening socket und die aller bisherigen Clients) in das Feld eintragen - select() aufrufen - danach schauen, was im Feld gesetzt ist darauf reagieren (neuer Client mit accept(), wenn der listening socket jetzt noch im Feld steht, für jeden Client-fd halt dessen Anfrage lesen und verarbeiten). - Feld mit den fds löschen - wieder oben anfangen... Dafür brauchst du keine Threads. Threads brauchst du für den anderen Weg: - in einer Endlosschleife accept() für den listening socket aufrufen (das blockiert, bis einer ein connect() macht) - bei jedem Rücksprung aus accept() mit dem neuen Client-fd einen Thread starten, der genau diesen Client bedient (in einer Endlosschleife): * Anfrage des Client lesen * bearbeiten * beantworten wieder anfangen bei Anfrage des Client lesen... Eine weitere Alternative wäre statt der Threads jeweils mit fork() einen neuen Prozeß zu erzeugen, der dann diesen Client bedient. Sowohl bei der Thread- als auch der fork()-Variante kann man den Thread bzw. Prozeß natürlich wieder beenden, wenn die Verbindung zum Client geschlossen wird.
Getestet hab ich das mit einfach ein paar Telnetsitzungen (< 100) und da stieg der RAM verbrauch merklich auf über 100MB (pro Client existiert ja dann ein Thread). Prinzipiell soll der Server als Statistikserver arbeiten für ein Spiel, das heißt Client verbindet sich, Client sendet ein paar Zeilen Daten, Client disconnected. Die Threads will ich deshalb, dass der Server auch neben den TCP Verbindungen auch noch andere Dinge tun kann, z.b. Statistik-Diagramme etc. erstellen. Die Clientanzahl kann ich nur grob abschätzen, aber ich kann mir vorstellen, dass durchaus Zahlen von 100 oder mehr Clients erreicht werden können. Wobei ja die Verbindung relativ kurz ist.
Nicht in Form von Klassen, aber als ausformulierte Demo-Beispiele, müsste ich noch als Quelltexte herumliegen haben. Genau genommen hier: http://mfgkw.dyndns.org/fhh/netzwerke.pdf Das ist zwar großteils unfertiger Text, aber die Quelltexte im Anhang B sind alle mal gelaufen. Das sollte eine relativ einfache Vorlage sein für select(), Threads und non blocking Deskriptoren (die hier wohl nicht sinnvoll sind, also ignorieren)
Ralf G. schrieb: > Prinzipiell soll der Server als Statistikserver arbeiten für ein Spiel, > das heißt Client verbindet sich, Client sendet ein paar Zeilen Daten, > Client disconnected. Dann vermute ich doch, daß du irgendwie vergisst, den Thread zu beenden? Der braucht doch nicht ewig laufen! Ralf G. schrieb: > Die Threads will ich deshalb, dass der Server auch > neben den TCP Verbindungen auch noch andere Dinge tun kann, z.b. > Statistik-Diagramme etc. erstellen. Das ist ok, geht aber auch mit select (indem man einen Timeout-Wert mit übergibt, dann am besten 0, und wenn kein Client zu bedienen ist halt etwas anderes macht, oder indem man einen Thread hät, der diese Arbeit macht und aselect und Client-Bedienung in einem anderen). Ralf G. schrieb: > ...dass durchaus Zahlen von 100 > oder mehr Clients erreicht werden können. Wobei ja die Verbindung > relativ kurz ist. Eben, es werden ja nicht beliebig viele gleichzeitig sein! Dann wäre es sogar vielleicht sinnvoller, die gleichzeitigen zu limitieren und neue ggf. etwas zu vertrösten. Wirklich arbeitende Threads in zu hoher Zahl bremsen sich ja nur gegenseitig.
Ich hab mir jetzt beide Beispiele mal angeschaut und mir ist dabei aufgefallen das ich den Fehler gemacht hab und trotz das ich select() hab weiterhin jeden Client mit accept() abgewartet hab. Ich werde das ganze jetzt mal überarbeiten und das Grundprinzip mit dem select() übernehmen. Danke erst mal für eure Hilfe. Wenn ich wieder auf Fehler und Probleme stoße melde ich mich wieder. Der Test mit den vielen Clients lief so ab, dass ich die Sitzungen alle gleichzeitig offen gelassen hab, somit konnten sich die Threads nicht beenden. Wenn man die Sitzung wieder geschlossen hat, dann hat sich auch der Thread wieder ordnungsgemäß geschlossen.
Ralf G. schrieb: > Getestet hab ich das mit einfach ein paar Telnetsitzungen (< 100) und da > stieg der RAM verbrauch merklich auf über 100MB (pro Client existiert ja > dann ein Thread). Wie groß ist denn der Stack?
Also wenn ich das richtig sehe dann hat der Stack ja diese 4 Schichten und davon ist doch für mich in erster Linie die 4te Ebene also Process für mich interessant oder ? Ich versteh nur noch nicht so ganz welche Größe du jetzt wissen willst.
Ja, da hast du recht. Du arbeitest/programmierst in der Application-Schicht. Darunter sind TCP (3) und IP (2) als Teil des Betriebssystems und der Netzwerkkartenkram mit Treiber (1). Das mit dem TCP/IP-Stack war jetzt von mir nicht so ernst gemeint; Rufus dachte wohl eher an den Programmstack - hatte das aber unvorsichtigerweise nicht ausdrücklich gesagt.
Es geht nicht um den TCP/IP-Stack, sondern um den Stack, den jeder Thread zugewiesen bekommt. Das ist der Speicherbereich, auf dem Rückspungadressen von Funktionen und automatische Variablen angelegt werden.
Hi, in dem Buch "Unix-Netzwerkprogrammierung mit Threads,Sockets und SSL" von Markus Zahn, ISBN-3540002995, werden genau diese Fragen, vor allen Dingen die verschiedenen Arten der TCP/IP-Server unter Unix, beantwortet. Mir hat es seinerzeit sehr weitergeholfen. Das gibt es inzwischen schon für 10 €! Gruß,
Rufus t. Firefly schrieb: > Es geht nicht um den TCP/IP-Stack, sondern um den Stack, den jeder > Thread zugewiesen bekommt. Das ist der Speicherbereich, auf dem > Rückspungadressen von Funktionen und automatische Variablen angelegt > werden. Irgendwie kann ich mir nicht vorstellen, daß es daran liegt. Die lazy allocation im Linux sorgt dafür, daß der Speicher erst dann tatsächlich belegt wird, wenn auch darauf zugegriffen wird.
Ralf G. schrieb: > ... oder schon einfache fertige TCP > Serverklassen ... Vielleicht auch interessant: In Qt gibt es auch fertige Klassen für den ganzen Netzwerkkram, u.a. auch für einen TCP-Server (QTcpServer). Ich habe damit nicht gearbeitet (mit Qt schon, aber nicht mit diesem Teil). -> http://qt.nokia.com/support Falls du dir das mal anschauen willst.
Rolf Magnus schrieb: > Irgendwie kann ich mir nicht vorstellen, daß es daran liegt. Die lazy > allocation im Linux sorgt dafür, daß der Speicher erst dann tatsächlich > belegt wird, wenn auch darauf zugegriffen wird. Das mag zwar sein, aber im virtuellen Adressraum müssen die Bereiche natürlich schon belegt werden. Vielleicht ist ja auch nur der Threadcode von Ralf so geschrieben, daß er viel Platz auf dem Stack benötigt - indem beispielsweise irgendwelche Sende- und Empfangspuffer als automatische Variablen darauf angelegt werden.
Rufus t. Firefly schrieb: > Rolf Magnus schrieb: >> Irgendwie kann ich mir nicht vorstellen, daß es daran liegt. Die lazy >> allocation im Linux sorgt dafür, daß der Speicher erst dann tatsächlich >> belegt wird, wenn auch darauf zugegriffen wird. > > Das mag zwar sein, aber im virtuellen Adressraum müssen die Bereiche > natürlich schon belegt werden. Klar. Das stört in der Regel aber nicht. Kann aber natürlich sein, daß Ralf als Speicherverbrauch gerade diese Größe angenommen hat. > Vielleicht ist ja auch nur der Threadcode von Ralf so geschrieben, daß > er viel Platz auf dem Stack benötigt - indem beispielsweise irgendwelche > Sende- und Empfangspuffer als automatische Variablen darauf angelegt > werden. Für >1MB pro Thread müssen das aber schon recht große Puffer sein. Außerdem ist es für den Speicherverbrauch letztendlich egal, ob die nun auf dem Stack oder wo anders liegen.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.