[C++/DISC] Afhandelen clients v.e. webserver

Pagina: 1
Acties:

  • yiko
  • Registratie: September 2003
  • Laatst online: 20-04-2025
Goeiemiddag allen tesamen.

Om mijn C/C++ kennis serieus omhoog te schoppen, ben ik begonnen aan een leuk project: mijn eigen webserver. Qua code sta ik absoluut nog niet ver. Ik ben nog voornamelijk aan het denken hoe ik alles zou organizeren qua structuur. En ik wil er ook bij vertellen dat het mijn bedoeling is dat het zowel onder windows als linux kan draaien.

Nu zit ik vooral te denken aan hoe de requests te verwerken.

Bij 1 client is het simpel. Connectie accepteren, volledige request inlezen en data sturen om vervolgens de connectie weer te sluiten.

Bij meerdere clients kan ik gemakkelijk werken met het select()-model.
code:
1
2
3
4
5
select loop
    indien connectie : accept it
    indien data beschikbaar : inlezen en toevoegen aan buffer
indien request volledig : data versturen
end of select loop


Als een client dan achter een grote file vraagt, moeten de andere clients wachten totdat die volledig doorgestuurd is. Is natuurlijk ook geen oplossing. Hiervoor vond ik als oplossing om maar telkens een stukje data te versturen (~1024 of zoiets). Dus op het einde van de select()-loop gewoon alle clients overlopen die nog data verwachten en een nieuw stukje versturen.

Dus waar ligt nu mijn probleem ? Ik zie het misschien te groot, maar ik zou in mijn webserver de mogelijkheid van een scripting taal willen inbouwen. Welke weet ik nog niet. Lua , Gamemonkey of misschien wel php. Maar hieromtrent heb ik dus nougabollen aan ervaring.

Maar met die scripts kom ik dan toch terug in een blocked state ? Een script mag nu voor mijn part 100mb aan data willen sturen naar de client, dit kan ik in stukjes doen. Als hij echter een half uur bezig is om die data aan te maken, kom ik in de problemen. Je kan moeilijk die verwerking “pauzeren” en bij de volgende select()-run, opnieuw effen wakker schudden om opnieuw te pauzeren. Het lijkt me dus dat ik niet veel anders zal kunnen dan met een thread per client te werken.

De vraag is natuurlijk hoe dit zich vertaalt naar resources / performantie.
Bij 3 clients zal het allemaal wel niet veel uitmaken, maar wat bij 1000 connected clients ?
Het zal waarschijnlijk nooit zover komen dat ik 1000 connected clients heb, maar het gaat om de oefening.

Onder windows kan ik werken met _beginthread (heb toch gelezen ergens op GoT dat deze het vriendelijkst is qua resources). Onder linux kan ik werken met oftewel fork() of pthread_* . Ik weet niet goed welke weg hier te kiezn. Pthreads lijkt mij op een gelijkaardige manier te werken als de windows threads, dus zal misschien gemakkelijker zijn om gelijkaardig te gebruiken.

Heb al eens zitten zoeken op threadpools, maar niets duidelijk gevonden. Dus weet ook niet precies wat het principe ervan is. Bijhouden van een aantal threads, die je dan kan hergebruiken ? Maar wat dan als je 1000 clients op een zelfde opgeblik hebt, kan je toch moeilijk met die 10 threads laten afhandelen ?
(als iemand een mooi linkje hieromtrent heeft, altijd welkom :) )


Dus alle suggesties / commentaar / opmerkingen / .. zijn welkom.
Alvast bedankt om tot hier te lezen ;)

  • Onno
  • Registratie: Juni 1999
  • Niet online
yiko schreef op woensdag 17 november 2004 @ 13:46:
Als een client dan achter een grote file vraagt, moeten de andere clients wachten totdat die volledig doorgestuurd is.
Dan moet je non-blocking I/O gebruiken. Als je een efficiente server wilt maken zou ik trouwens geen select maar iets als epoll (Linux) of kqueue (BSD) gebruiken met edge triggering. :)

Over threading voor scripts enzo: bekijk de verschillende modellen die Apache gebruikt eens. Over het algemeen is de aanpak ongeveer als volgt: zorg ervoor dat je altijd een paar idle workers (of dat nou threads, processen of een combinatie daarvan zijn maakt hiervoor niet echt uit) hebt; maak er meer als je onder een minimum aantal van idle workers komt (maar nog onder een vast maximum zit); kill er weer een paar als je meer dan een maximaal aantal idle workers hebt. Als je op je maximum aantal zit en er komen toch nog meer requests binnen, dan hebben die gewoon pech en moeten ze wachten. Het heeft totaal geen zin om te proberen om 141414 requests tegelijk af te handelen als je server dat toch niet aankan. :)

[ Voor 7% gewijzigd door Onno op 17-11-2004 14:08 ]


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Je kan idd het best met threadpools en mischien met a-synchrone sockets werken.
Threadpools werken op de volgende manier:

Een client doet een request. Je server gaat dan in zijn thread pool kijken of een vrije thread is. Zo ja dan assignt hij deze thread aan je client en die handelt het request af. Als de request afgehandeld is dan zal de thread weer vrijgegeven worden in de thread pool.

Als er geen vrije thread is zijn er 2 mogenlijkheden. De eerste is dat je de request laat wachten totdat er een thread vrij komt. Het nadeel hiervan is natuurlijk dat een request erg lang kan duren omdat er toevallig net een paar requests bezig zijn die lang duren.
De tweede mogenlijkheid die je hebt is om op dat moment een extra thread aan te maken en je thread pool dus zo uit te breiden. Eventueel kan je je thread pool ook automatisch weer laten inkrimpen op het moment dat er maar weinig thread gebruikt worden.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Verwijderd

Allereerst over windows/linux. Als je op beide platformen wil draaien ga dan zoveel mogelijk platform neutraal programmeren. Gebruik componenten die op beide OSen aanwezig zijn en vermijd fancy trucken.

Om wat meer op dit project in te gaan. Er zijn natuurlijk complete crossplatform TCP-IP servers die het hele thread verhaal voor je afhandelen. Ook hoeft een thread niet per se noodzakelijk te zijn voor een webserver. Threads veroorzaken namelijk een hoop overhead ten opzichte van blocking servers en in het geval dat je simpele statische pagina's weergeeft zijn ze vaak niet nodig.

Voor processen die relatief lang duren zijn threads echter wel onmisbaar, maar een timeout is eigenlijk ook altijd noodzakelijk. Als je process vastloopt wil je niet dat die thread oneindig lang resources blijft consumeren. Zowel forks als pthreads kunnen op linux gebruikt worden en met beide methoden kun je zonder problemen een webserver ontwerpen. Maar als je crossplatform wilt zijn, is het handiger om pthreads te gebruiken. Die lijken redelijk veel op windows threads en het is mogelijk om een superclass myownthread te maken die op windows gebruik maakt van threads en op linux van pthreads. Hierdoor kun je de platform afhankelijkheden concentreren in die ene klasse en is de rest van je code voor beide OSen bruikbaar.

Tot slot connection pooling. Bij een webserver hoef je gebruikers niet verbonden te houden met de server. Normaal gedrag is: connect, vraag pagina op, retourneer data, disconnect. 1000 gelijktijdige connecties is dan ook erg veel. Om performance redenen ondersteunen vele websites ook keep-alive mogelijkheden waarbij de verbinding niet verbroken wordt na het verzenden.

Bij connection pooling wordt de serverside thread die de request afhandeld niet elke keer opnieuw gegenereerd maar uit een bestaande pool van threads gehaald. Bij normale verbindingen wordt de thread na het versturen van de data weer teruggestopt in de pool. Bij keep alive connecties blijft de thread in gebruik zolang de client verbonden is. Echter, er is niemand die de server tegenhoudt om een keep-alive verbinding te verbreken. Dus als je threads uit de pool op zijn kun je gaan kijken welke keep-alive threads op dat moment niets doen, die verbreken en hergebruiken voor een andere request. Ook kun je natuurlijk threads bijmaken maar op een gegeven moment is het geheugen op en zul je threads moeten gaan hergebruiken. Dus een stukje housekeeping wat dode threads verwijderd en ruimte creeert voor nieuwe threads is ook noodzakelijk, of je nu gebruik maakt van connection pooling of niet.

  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Je zou i.p.v. een script interpreter in je process, CGI kunnen implementeren. Dat is niet veel meer dan een programmatje opstarten, de data die jij binnen krijgt naar de stdin van dat programmatje overpompen en de output weer terugsturen. Ik denk dat zoiets dergelijks eenvoudiger in een "select()" model te integreren is, dan allerlei dingen als php via multi-threading, etc.

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Verwijderd schreef op woensdag 17 november 2004 @ 14:19:
Bij keep alive connecties blijft de thread in gebruik zolang de client verbonden is.
Dit hoeft toch niet noodzakelijk. Je kunt best je thread weer in de thread pool stoppen en je Connection open houden. Als je gebruik maakt van non-blocking IO kun je gewoon op het moment dat de gebruiker over de zelfde connection een nieuwe request doet weer een thread toewijzen. Zo voorkom je ieder geval dat een thread op een client loopt te wachten. Ook al heb je keep alive connecties dan hoeven verschillende request nog steeds niet noodzakelijk door dezelfde thread afgehandeld te worden.

[ Voor 3% gewijzigd door Woy op 17-11-2004 14:37 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 15-05 06:45
Als je scripting engines in je webserver wil draaien, dan zul je hoe dan ook threads moeten gebruiken. Het select-model kan dan dus overboord. Het select-model is alleen geschikt als je je wil beperken tot I/O operaties. CGI (en alle systemen waarbij je een extern proces opstart voor je scripting engine) kun je wel met een select-model implementeren.

Uiteindelijk is een select-model gewoon beperkt. Het is een geschikt model als je de gewenste mogelijkheden duidelijk afgebakend hebt, en je niet van plan bent later scripting engines in de webserver te ondersteunen. De perfomance van een implementatie op basis van een select-model is relatief goed, maar een multi-threaded systeem is flexibeler.

  • yiko
  • Registratie: September 2003
  • Laatst online: 20-04-2025
Mijn gedacht van een threadpool was dus juist. Het lijkt me ook wel leuk om dit te proberen te implementeren.
De link van Onno lijkt me handig, en zal ik eens op mijn gemak doornemen. Alvast bedankt.

Een eerste versie zal misschien alleen maar bestandjes behandelen, en zal ik waarschijnlijk eens via het select() mechanisme doen. Als ik dan een scripting engine erin wil plooien, kan ik dan eens kijken voor via threads te werken. Dan kan ik meteen ook eens de performantie tussen de twee manieren benchmarken. :)

Er staat mij veel werk te wachten, maar ik zie het wel zitten :)

  • 12_0_13
  • Registratie: April 2004
  • Laatst online: 12-02 13:19
Volgens mijn is de pthread lib geschikt voor zowel windows als linux :) Dus dat is een aanrader ;)

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Boost www.boost.org heeft ook een platform-onafhankelijke thread-library: http://boost.org/libs/thread/doc/index.html. Ik heb het verder nooit gebruikt, maar over het algemeen werkt het spul van boost wel mooi, dus...

  • yiko
  • Registratie: September 2003
  • Laatst online: 20-04-2025
MrBucket schreef op woensdag 17 november 2004 @ 18:30:
Boost www.boost.org heeft ook een platform-onafhankelijke thread-library: http://boost.org/libs/thread/doc/index.html. Ik heb het verder nooit gebruikt, maar over het algemeen werkt het spul van boost wel mooi, dus...
Ik ben altijd een beetje bang om een grote library te gaan gebruiken. Je moet dan eerste die library leren kennen , en meestal gebruik je ook niet alles ervan. Kwil het ook zo klein mogelijk houden.

De pthreads library voor windows heb ik ook net tegengekomen. Het lijkt mij wel interessant om deze eens te bekijken. Kzal toch zowiezo pthreads onder de knie moeten krijgen.

Verwijderd

rwb schreef op woensdag 17 november 2004 @ 14:37:

Dit hoeft toch niet noodzakelijk. Je kunt best je thread weer in de thread pool stoppen en je Connection open houden. Als je gebruik maakt van non-blocking IO kun je gewoon op het moment dat de gebruiker over de zelfde connection een nieuwe request doet weer een thread toewijzen. Zo voorkom je ieder geval dat een thread op een client loopt te wachten. Ook al heb je keep alive connecties dan hoeven verschillende request nog steeds niet noodzakelijk door dezelfde thread afgehandeld te worden.
Klopt natuurlijk, maar het is wel zo dat de meeste kant en klare TCP-IP servers die asynchroom werken de thread samen met de connectie creeren. Het kan dus inderdaad anders, maar de koppeling wordt wel vaak gemaakt.
Pagina: 1