www.mikrocontroller.net

Forum: PC-Programmierung C++ TCP MultiClient Server


Autor: Ralf G. (sense)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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)?

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Ralf G. (sense)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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)

Autor: ... ... (docean) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Ralf G. (sense)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Ralf G. (sense)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was genau meinst du mit Stack ?

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Der TCP/IP-Stack ist insgesamt 4 Schichten hoch :-)

Autor: Ralf G. (sense)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Oliver R. (superberti)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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ß,

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.