Forum: PC-Programmierung C++ TCP MultiClient Server


von Ralf G. (sense)


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)?

von Klaus W. (mfgkw)


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.

von Klaus W. (mfgkw)


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.

von Ralf G. (sense)


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.

von Klaus W. (mfgkw)


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)

von ... .. (docean) Benutzerseite


Lesenswert?


von Klaus W. (mfgkw)


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.

von Ralf G. (sense)


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.

von Rufus Τ. F. (rufus) Benutzerseite


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?

von Ralf G. (sense)


Lesenswert?

Was genau meinst du mit Stack ?

von Klaus W. (mfgkw)


Lesenswert?

Der TCP/IP-Stack ist insgesamt 4 Schichten hoch :-)

von Ralf G. (sense)


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.

von Klaus W. (mfgkw)


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.

von Rufus Τ. F. (rufus) Benutzerseite


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.

von Oliver R. (superberti)


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ß,

von Rolf Magnus (Gast)


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.

von Klaus W. (mfgkw)


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.

von Rufus Τ. F. (rufus) Benutzerseite


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.

von Rolf Magnus (Gast)


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.

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.