Egy kis erőforrásigényű webszervert készítek, az LWIP tcp kezelésére alapozva, de nem sikerül megtalálnom az optimális HTTP csomagküldési módszert.
Ha az LWIP netconn csomagjára bízom a teljes pufferkezelést, akkor viszonylag gyakran előáll a termelő-fogyasztó probléma, emiatt befagy a kérelem kiszolgálása.
Emiatt az LWIP raw csomagját használom, ami szép gyorsan képes kiszolgálni a kérelmeket, de sehol semmilyen dokumentációt nem találok arról, hogy hogyan kellene generálnom egy HTTP kérésre a választ.
A példák alapján:
- ha tcp-echo szerű válaszokat akarnék, azaz egy csomagra egy csomagot válaszul, akkor a kérelem TCP csomag fogadásának (tcp_recv) eseményénél rögtön el is küldhetem tcp_write művelettel a válasz csomagot.
- ha fix, nagyméretű pufferből akarnák választ kiszolgálni, akkor a beérkező csomag fogadásánál elküldök egy tcp_sndbuf() által meghatározott maximális méretű csomagot, majd a csomag fogadásának visszaigazolása után egy újabbat és egy újabbat ...
De mi van, ha dinamikusan akarok sok válaszcsomagot küldeni egyetlen beérkező tcp kérelemre?
Ha a tcp_recv eseményénél elkezdem tcp_write()-tal teleírni a puffert, a válasz soha nem jut el a kérelmezőhöz.
Olyan, mintha a tcp_recv esemény lefutása után kezdené csak el kiüríteni a tcp_write()-tal teleírt puffert. Azaz pici, rövid válaszokat könnyű itt generálni, de nagy hosszú válaszokatat nem. S bár van olyan művelet, amivel tudom jelezni, hogy a kérelem csomagja már feldolgozva, ezek után sem kezdődik meg rögtön a válasz elküldése.
Mivel a dokumentációk sehol sem említik ezt, azt várnám, hogy a tcp_write()-tal teleírom a puffert, ami a háttérben, azonnal elküldésre kerül, amint lehetőség nyílik rá. Tehát írok, majd várok, ha már nincs hely a tcp puffer-ban, és ha kiürült a puffer, folytatom tovább az írást.
Ez azonban rém lassú kommunikációt ereményez. Ha néha manuálisan beteszek egy tcp_output() műveletet, hogy küldje ki a puffert, ez gyorsít, de még mindig nem azonnali a válasz.
Nem tudom, hogyan kellene használnom a tcp_write parancsot hogy optimálisan tudja visszaküldeni a választ.
Jelenleg a legjobb megoldásom, hogy a kérelem fogadásánál elindítok egy külön szálat, ami elkezd tölteni egy puffert, és az eredeti szálban pedig figyelem, hogy van-e a pufferban adat, ha igen, akkor a tcp_write()-tal elküldöm, míg a puffer ki nem ürül, és lezárásra nem kerül. Ekkor én is lezárom a tcp kapcsolatot.
Az eredmény bár működik, de mégis nyökögős és siralmas.
A tcp_write már maga is kezel egy puffert, teljesen feleslegesnek tűnik párhuzamosan még egy kezelése. De nem tudom, mi lenne az optimális módja a csomagok küldésének. És akkor még a POST kérelmekről nem is beszéltem, ahol a POST adat egy külön csomagban érkezik, tehát nem elég az első fogadott csomagot ismernem ahhoz, hogy válaszoljak. Természetesnek tűnik, hogy akkor először fogadjunk minden csomagot, és ezek után kezdjünk csak el válaszolni, de ez meg túl erős megkötésnek tűnik, ha például egy fájlfeltöltést akarnánk kezelni, ahol POST-ban jön a feldolgozandó adat folyamatosan.
Tehát minden beérkező kérelem esetén kellene tudnom valamekkora választ generálnom, és ezt folyamatosan vissza kellene tudnom juttatni a kliens felé, függetlenül a válasz méretétől. Ha pedig megtelik a puffer, valahogy várakoztatnom a válasz generálását.
A feladatot csak nehezíti az, hogy nem túl sok erőforrás áll a webszerver rendelkezésére, így az nem megoldható, hogy először legenerálom a teljes választ egy pufferbe, majd onnan szépen egyesével kiküldöm a csomagokat, mivel a teljes válasz egyszerűen nem fér el a memóriában.
Ha valakinek van tapasztalata alacsony szintű webszerver készítésében, érdekelne, hogyan oldotta meg ezt a problémát.
De, ha tudtok egy jó leírást arról, hogy pontosan hogyan is kell zajlania a HTTP alatt a csomagok cseréjének, és ezt az LWIP hogyan támogatja, az is nagy segítség lenne.
És köszönöm, már azt is, ha csak eddig elolvastad. ;)
- 1361 megtekintés
Hozzászólások
A TCP szallitasi protokoll, a HTTP alkalmazasi protokoll. Ha a kliens akarja, akar 1 bajt/csomag is kuldheti a kerest es te is ra a valaszt. Akkor amikor a HTTP keres az RFC szerint befejezodott, akkor kell kuldened a valaszt. Ha csak mindig 1 oldalt adsz vissza barmely keresre, akkor mindent eldobhatsz kozben, csak figyeld a ket egymas utani CRLF-t.
- A hozzászóláshoz be kell jelentkezni
Köszönöm, megnéztem én is az RFC-t és valóban azt írja, hogy a választ a kérelem beérkezése után kell küldeni. Ezek szerint a példák azért tudják közvetlenül a kérelem fogadásakor elküldeni a választ, mert az pici, belefér az lwip pufferébe, és a kérelem fogadása után kerül csak kiküldésre a valóságban.
A problémám igazából az, hogy nem tudom, mikor ér véget a kérelem. Ha elküldi a kérelem a headerben a hosszát, akkor tudom figyelni, de ha nem - és általában nem -, akkor miből tudhatom, hogy jön még csomag a kérelemben? Azt tudom, ha a kérelmező lezárja a kapcsolatot, de akkor meg már késő.
- A hozzászóláshoz be kell jelentkezni
Ha HTTP/1.1 szerint mesz akkor nem kotelezo a kliensnek becsukni a sajat oldalat, siman nyitva tarthatja esetleges tovabbi HTTP keresek kuldesere, hacsak a szerver nem valaszol Connection: close fejleccel. A request fejlec ket sortoresre er veget, es vagy van Content-Length fejlec, vagy nincs es akkor chunked encodinggal jon a request body. Sot mi tobb, ha a pipelining tamogatott, akkor akar azonnal kuldheti a kovetkezo kerest, amire sorrendben kell valaszolnod. Ha HTTP/2-t is akarsz tamogatni, akkor pedig egy binaris streammultiplexalos mokat kell dekodolni.
--
Pásztor János
Sole Proprietor @ Opsbears | Refactor Zone
- A hozzászóláshoz be kell jelentkezni
HTTP/1.1 RFC, 4.4-es pont, nagyon röviden:
- Ha nincs body, az első üres sor után vége a requestnek
- Ha van body, akkor vagy van Content-Length, vagy Chunked típusú a kérés, (vagy a kódolás/formátum tükrében levezeted magadnak).
- A hozzászóláshoz be kell jelentkezni
Ezek szerint, ha POST kérés esetén mindig lesz Content-Length, GET kérésnél pedig nincs body. Ez így szép, kerek és könnyen kezelhető, köszönöm!
Már csak az az egyetlen bizonytalan pont, hogy hol tudom generálni a válasz tartalmat.
Még az lwip saját http szerver példája is egy előre legenerált html kódot küld vissza darabokban. Egyes részeit kicserélheti ugyan előre legenerált dinamikus tartalmakkal (SSI), de a lényeg, hogy először generálja az adatot, és utána már csak küldi a választ a tcp puffer méretére darabolva. A válasznak egyben elő kell állnia küldés előtt.
Én generálás közben szeretnék választ küldeni, de nincs olyan tcp esemény, hogy kezdődhet a küldés.
A kérelem fogadása alatt még akkor sem tudok választ küldeni, ha használom a tcp_output() műveletet, ami elvileg kikényszerítené a csomag elküldését.
Van még a tcp_poll() eseményem, ami meg már feltételezi, hogy várakoztam, tehát nem azonnal küldöm a választ.
Logikailag nem látom tisztán, hogy mi lenne a módja, az azonnali válaszküldésnek, ha a válasz nem fér bele a tcp pufferbe, és nem tárolható el előre. A kérelem fogadásának befejezése után, a fogadó eseményből kell egy külön szálat indítanom, ami tölti a tcp puffert, és ha megtelt, vár, mivel az lwip a tele puffert úgyis automatán elküldi? (Sok webszerver példában egyáltalán nincs tcp_output() művelet.)
- A hozzászóláshoz be kell jelentkezni
POST kérés esetén mindig lesz Content-Length
Nem! POST, PUT, stb. keresnel LEHET Content-Length, VAGY a Transfer-Encoding erteke "chunked" lesz, amely esetben a chunked encodingbol kell kiolvasni hogy hol a vege. Lasd: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding. Raadasul az sem garantalt, hogy a GET eseten nem lesz body, noha nem szokvanyos, de lattam mar.
--
Pásztor János
Sole Proprietor @ Opsbears | Refactor Zone
- A hozzászóláshoz be kell jelentkezni
A chunked transfer encoding nekem új, eddig még nem találkoztam vele. Az előbb ránéztem, és azt írták róla, ez streamek küldéséhez használatos, ezért gondoltam, hogy általban egy POST, nem így fog jönni. A linken szépen leírják hogy megy, így ez esetben is könnyen el tudom majd dönteni, hol ér véget a kérelem. Köszönöm!
A GET esetén, ha jön is bármi más is, azt el fogom dobni. Nem is tudom, mihez tudnék kezdeni vele?
- A hozzászóláshoz be kell jelentkezni
A chunked encodingot implementalnod KELL, hacsak nincsenek az ellenorzesed alatt a kliensek, mert azt a kuldo fel donti el hogy hogy kuldi az adatot. Akkor hasznalatos amikor a kliens nem tudja elore mekkora lesz a request, pl. egy masik helyrol (DB-bol) olvassa, vagy dinamikusan generalja es el akarja kerulni az egesz request buffereleset memoriahatekonysagi okokbol.
GET eseten eldobhatod a bodyt, de attol meg kezelned kell hogy esetleg valaki kuldi es utana ugyanabban a TCP csatornaban jon a kovetkezo keres.
Nem tudom, hogy mi a cel ezzel a projekttel, de ha egy HTTP/1.1 compliant webszerver keszitese, akkor egy zsak minden van amit implementalnod kell, olvasd el a vonatkozo RFC-ket. Hacsak kvazi-compliant kell, akkor nezd meg hogy mit kuldenek a kliensek amiket tamogatni akarsz es hajra.
--
Pásztor János
Sole Proprietor @ Opsbears | Refactor Zone
- A hozzászóláshoz be kell jelentkezni
Mivel általában én is Content-Length nélkül küldöm a választ, nekem is illene chunked enodingot használnom, vagy ez csak a kérelmekre vonatkozik, és teljesen korrekt, ha a csatorna lezárásával jelzem, hogy vége a válasznak?
- A hozzászóláshoz be kell jelentkezni
Vagy kuldesz Content-Length fejlecet vagy hasznalsz chunked encodingot. Minden mas esetben nem garantalt hogy a kliens elolvas mindent es random bugok kerulnek elo. HTTP/2-nel nem tudom hogy a HTTP framek ezt megoldjak-e, meg nem melyedtem bele. Tenyleg el kellene olvasni azt a franya RFC halmazt ha korrekt szervert akarsz implementalni.
--
Pásztor János
Sole Proprietor @ Opsbears | Refactor Zone
- A hozzászóláshoz be kell jelentkezni
HTTP/1.1-ben ha valamelyik fel zarja a kapcsolatot, akkor vege. Nem a legelegansabb, sot, de RFC szerint mukodik
- A hozzászóláshoz be kell jelentkezni