TCP stream olvasása

Fórumok

Igazából több technika van rá C++-ban, de a véleményetekre lennék kíváncsi, melyikkel lehet a leghatékonyabban olvasni egy olyan TCP stream-et, ahol az adatok <...adat...> jelek közé esnek. Még cél, hogy az esetleges hibákat kezelje, azaz ha zárás nélkül jön megolt kezdés, nincs kezdés, stb.

1. byte-onként olvasna az adatokat és töltene egy memória foglalást, amit lépésenként növelne
2. fix méretet olvas, abban keresi a záró tagot, és ebben az egységben tölti és növeli a foglalt memóriát.
3. feltölt egy előre allokált memória területet, és teljesen másik eljárás kiszedegeti belőle a szükséges adatokat.

Tehát programozás technikailag az olvasás végző program egységre (függvény/objektum/...) bíznátok a tagolást, vagy inkább egy másik program részre.

Hozzászólások

Úgy "elegáns", hogy ha csinálsz egy "parser" class-t, ami parsolja a stream olvasó class által felolvasott adatokat.
De majd a tapasztaltabbak megmondják, és lehet, hogy simán csinálnék egy class-t, ami olvas és parsol egyben.

1. mindenképpen (méretes) pufferbe olvasunk, mivel karakterenként baromi lassú tud lenni, persze ha a 9600bps-es soros vonalon jönnek a dolgok, akkor kb. mindegy...
2. ha van értelmes felső korlátja az olvasandó input egy sorának/rekordjáak, akkor érdemes a teljes puffert akkorára venni, hogy az beleférjen, különben nyilván valamilyen dinamizmus szerint kell egyre nagyobb memóriaterületet foglalni.
3. a pufferben aztán lehet keresgélni, hogy megjött-e már a végjelzés. amikor megvan, akkor allokálunk egy akkora memóriaterületet, amekkora adat jött, oda egyszer másolunk, a puffert pedig tetszés szerint vagy körkörösen használjuk (így nincs másolás, de bonyolultabb a kezelése), vagy minden sor/rekord kimásolása után "előreigazítjuk" a bennmaradt részt, ami sok másolást jelenthet, ha a pufferben ott van már a következő sorból/rekordból valamennyi.

Ha a protokollt en irom, es sender oldalon tudom, hogy mekkora adat fog kovetkezni, akkor en a kommunikacio elejen jeleznem, hogy mekkora adat erkezik, es ezzel az infoval a zsebeben a kliens mar tudja magat meretezni.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Semmi sem garantalja, hogy ha N bytonkent pakolsz adatot a csatornaba a kuldo oldalon, akkor ugyan ugy N byteos csomagok fognak erkezni a fogadonal. Az operacios rendszernek joga van akar 1 byteonkent is atadni neked az informaciot. Persze ez eleg nagy butasag volna, de nincs ra garancia, hogy nem ez fog tortenni. Minden esetre a buffered meretet sose epitsd a felette levo protokolra. A buffer lehetoleg legyen a lapmeret tobbszorose (akar 1 is, a vart adatok meretetol fuggoen), es legyen laphatarra igazitva. ("man -s 3 memalign" a baratod)
A legjobb megoldas ha keszitesz egy bufferkezelot, ami elrejti eloled a read() altal eppen visszaadott adat meretet. Ez a bufferkezelo birtokolhatja a buffert, es igy folyamatosan ugyan arra a lapra olvashatsz a read-el, amig nem lesz eleg adatod, hogy a felette levo reteg (a protokolod) feldolgozza.

Ertem, de nem is egy csomagrol, hanem kettorol beszelunk. Arra gondoltam, hogy nem biztos hogy jo dolog, ha folyamatosan sok memoriat tart maganal a pufferkezelo - feleslegesen. Peldaul ha nagyon eltero csomagmennyisegek jonnek, akkor nem biztos, hogy van ertelme mindig akkora puffert magamnal tartani, amekkora az eddig bejott legnagyobb csomag volt. Ez foleg olyan helyeken fontos, ahol azert nem gigabyte-s memoriameretekrol beszelunk.

Ezert gondoltam arra, hogy van egy puffer, az akkora, hogy az informacios csomag mindenkeppen beleferjen, de legalabbis nem sokkal nagyobb. Ha megjott az info csomag, akkor odaszolok a puffernek (vagy a kezelojenek, ez most mindegy), hogy most ekkora csomag fog erkezni, keszulj fel ra. Es felkeszul. Ha megjott a csomag, akkor - miutan leurult a puffer - egyszeruen visszaall a korabbi meretre, ezzel felszabaditva egy csomo memoriat, amit egyebkent feleslegesen foglalna.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

en meg nem lattam olyan szoftvert ahol sikerult volna ertelmesen kezelni a sikertelen memoriafoglalast. ha pedig ertelmesen nem lehet, akkor legjobb sehogy, hogy legalabb a stacktracen latszon, mibe halt bele.
megjegyzem olyan problemaval sem talalkoztam meg, hogy par M buffer ne lett volna eleg sok GByte/sec forgalom kezelesere.
Ne kuzdjetek szelmalmokkal gyerekek, 1M buffer *BIZTOSAN* eleg lesz, ne nehezitsd meg a sajat eleted mindenfele okos algoritmusokkal amikor nincs is problemad.

en meg nem lattam olyan szoftvert ahol sikerult volna ertelmesen kezelni a sikertelen memoriafoglalast
Jajj! Tanulni, tanulni, tanulni! Attol, hogy egy program adott idoben nem jut hozza a kivant eroforrashoz meg nem kell hogy kinyuvadjon.

Ne kuzdjetek szelmalmokkal gyerekek, 1M buffer *BIZTOSAN* eleg lesz, ne nehezitsd meg a sajat eleted mindenfele okos algoritmusokkal amikor nincs is problemad.
Jajj++

termeszetesen nem kell, hogy kinyuvadjon. alternativ megoldaskent nem kell 200+MByteos buffereket allokalni csak azert mer 'a memoria occso'.
termeszetesen el lehet menni mindenfele memoriafoglalasi hibakat kezelgetni, de ha a programod nagyobb, mint ami elfer a 32 (48) biten, akkor mar ****REGEN**** mindegy, hogy kezeled-e, vagy nem. maskepp fogalmazom: nem azert lesz szar, mert nincs lekezelve, hanem mert kezelni kell.
kivancsi vagyok milyen adatforgalommal talalkoztal mar, amit nem tudtal amugy 1M-os bufferrel kezelni, mert en meg semmilyennel.

Platform független kódban egyébként is kerülöm a realloc-ot, mert MacOS-en vagy OS.X-en volt vele baj.

Az, hogy ronda, az meg szubjektív. Én sokkal jobban elvagyok ezzel, mintha sajátot kéne írni vagy minden kilépő pontnál hívogatni kéne a free-t.
--
http://www.naszta.hu

A vector realloc-olhat, ha növeled (és csökkentéskor sincs megtiltva neki, implementációfüggő). Nézegesd a size() és capacity() függvényeit különböző átméretezések esetén.

Amúgy a realloc() is másol, ha új területen talál csak nagyobb egybefüggő helyet.

Ha neki valamiért a vector szimpibb, akkor ne beszéljük már le róla, érthető előnyei vannak, pl. tömb és elemek managementje, típusellenőrzés, valós és használt méret transzparens kezelése, miegymás. Ha szeret pointereken lovagolni, akkor meg úgyis malloc-realloc-free-t fog használni.

A realloc azt csinalja, hogy fogja az eredeti, malloc altal visszaadott virtualis cim alatt levo fizikai lapot (most nyilvan lapmeretnel nagyobb foglalasokrol beszelunk), es beteszi egy olyan virtualis cim ala, ahova elfer az uj kivant meret. Ha a kivant meret elfer ott, ahol az elozo foglalas volt, akkor nem valtozik a cim. ha nem, akkor a virtualis cim valtozik, viszont az adatokat(a donto tobbseguket, amik egesz lapokat toltenek ki) *nem* masolja at az uj helyre, ez a lenyeg.
Ebbol nemi belegondolassal ra lehet jonni, hogy a vector resize() muvelete nem reallocolhat, ugyanis a realloc mas virtualis cimet adhat(es jo esellyed ad is) vissza. A c++ ilyenkor mindenfele konstruktor es destruktor hivasokat garantal, amik *nyilvan* nem tudnak megfeleloen vegbemenni, ha az adatokat csak ugy atpakolja a realloc az egyik virt. cimrol a masikra. Megtiltva amugy tenyleg nincs, mint ahogy az fseek() hasznalata sem, csak ugyan ugy nem celravezeto egyik sem vector implementalasakor.

Amugy az egy alapvetoen rossz erveles, hogy "nincs megtiltva, hogy ugy legyen implementalva". Ha nem *tudod*, hogy az altalad hasznalt platformon ugy van-e, vagy nem, akkor *ne* feltetelezd mert akkor fogsz rajonni, hogy baj van a teljesitmennyel amikor mar regen keso, es kezdhetsz heggeszteni.

A ,,nincs megtiltva'' részemről nem érv volt, az érveim a 3. bekezdésben vannak.

A realloc()-ról az infót köszi, ez jó.

Azért nem hiszem, hogy egy parser-ben (jó esetben) több lapnyi területet igénylünk.

Amúgy mindenféle STL-es cuccnak az egyik jópofa jellemzője még, hogy lecserélheted a default allocator-t valamire, ami Neked jobban passzol a feladathoz. De most nézegettem itt strace-szel nagyobb blokkok foglalása, átméretezése mmap2() hívásban köt ki, azaz vagy realloc-kal csinálja, vagy mással, de lapoz; és ez az alapértelmezett allocator, nem nyúltam hozzá.

Szerk: Ja, a vektor objektum címe nem egyenlő a lefoglalt terület címével. A területet nyugodtan átteheti más címre. Minden le van zsírozva, [] operátornak, destruktornak, minden tagfüggvénynek, stb. meg van mondva, hogy melyik címet használja.

a problema nem azzal van realloc()ot hasznalo vector::resize() eseten, hogy a vector nem tudja majd elerni az uj elemeket (ugye ez eleg amator hiba volna), hanem hogy a c++ nyelv mindenfele konstruktor es destruktor hivasokat garantal az athelyezett objektumoknak. konkretan masolo konstruktor hivasokat az ujaknak, es destruktor hivasokat a regieknek. emellett teljesen ertelmetlenne valik a realloc, ugyanis nem byteonkenti masolas, hanem sima inicializalas tortenik. az, hogy RAW atbaszod az objektumokat egy masik teruletre nem eleg.
es nem parserrol, hanem TCP stream bufferrol van szo, amit majd egy parser fog olvasgatni. az en kedvenc oprendszerem szinte mindig 4Ks lapkat hasznal, annal azert tobb adatot szoktam fogadni egy tetszoleges TCP csatornan.
az allocator meg szep dolog, bar kicsit tulsagosan hypeolt, es annyira azert nem major featurenek tartom. nagyon nehez hasznalni, es konkretan en meg nem talalkoztam olyan allocator implementacioval ami nem-szintetikus tesztben jobban teljesitene mint a gcc stl standard allocatora.

A legjobb megoldas ha keszitesz egy bufferkezelot, ami elrejti eloled a read() altal eppen visszaadott adat meretet. Ez a bufferkezelo birtokolhatja a buffert, es igy folyamatosan ugyan arra a lapra olvashatsz a read-el, amig nem lesz eleg adatod, hogy a felette levo reteg (a protokolod) feldolgozza.
Es honnan a fenebol tudja a bufferkezelo, hogy mikor van eleg adat, amikor nem ismeri az adott protokollt, fogalma sincs hol kezdodik, illetve vegzodik az adat?

nem tudja. amikor van K byteja, es olvasott N-et, akkor felajanlja az N+K bytejat a felette levo retegnek. Az elkezdi parsolni, es ha befejezett egy alkalmazas szintu uzenetet, akkor szol a buffernek, hogy az elso X byteot eldobhatja. igy marad a bufferben N+K-X byte. amikor a current pointer a buffer felen tul van (vagy 3/4-en, izles kerdese) akkor a meg fel nem dolgozott byteokat eloremasolod, es hasznalod megint. Ha van benned artistic hajlam, akkor a kedvenc oprendszered nativ lapkezelo algoritmusaival elcsusztatod azokat a lapokat amin az aktualis fel nem dolgozott buffer van a buffered virtualis cimterenek az elejere, es meg a memcpy()-t is megsporoltad.
igy a bufferkezelo nem tud a felette levo protokolrol, es barmit rapakolhatsz, valtozo csomaghosszu protokolokat is konnyeden.

Es honnan a fenebol tudja a bufferkezelo, hogy mikor van eleg adat, amikor nem ismeri az adott protokollt, fogalma sincs hol kezdodik, illetve vegzodik az adat?
igen, a bufferkezelo"nek "valamennyire" azert ismernie kell a protokollt. peldaul, ha sima mezei master-slave, sorveg-terminalt parancs-egysegekkel (lasd smtp, http, ...), akkor egy fgets() szeru" valami ma'r eleg lehet. persze az fgets() az elegge primitiv egy ilyen celra, de mint kiindulas nem rossz. gyakorlatban valami ilyesmit lehet csinalni.

javaslom, h elobb nezd at az alatta levo API-kat. read(2), write(2), kqueue(2), select(2), poll(2). utana pedig tuzetesen olvasd at ezt: http://www.kegel.com/c10k.html

aztan vilagos lesz a dolog. annyit tennek meg hozza, h bar byte-onkent lehet olvasni, de nem ajanlott. olyan IO terhelest csinalsz vele, h halalra iteled a sajat alkalmazasodat, es kilovod vele a CPU-dat. pufferelj rendesen, vannak syscallok, amik megmondjak, h mennyi adatot tudsz irni/olvasni egy socketbol (sot, van olyan event notification API is, ami ezt neked kapasbol meg is mondja, pl kqueue).

Igen, az egy hasznos link, bar alapvetoen tulmutat a kerdesen ;) De hogy visszakanyarodjunk a temahoz, a bajtonkent olvasas nyilvan nem a jo megoldas, vagy valasztasz egy fix meretu buffert (maximalis adathossz) es azt mondod, hogy azt az adatot, ami ennel hosszabb eldobod (ez nyilvan adatvesztessel jarhat, ha rosszul valasztod a meretet), vagy dinamikusan kezeled a buffer meretet valasztva egy minimum (valoszinusitheto adathossz) es maximum meretet. A maximum azert tanacsos, mert nem lehetsz biztos benne, hogy a beerkezo adat nem vegtelen hosszu. Hogy a dinamikus buffered hogyan implementalod alapvetoen toled es a hasznalt kornyezettol fugg, pl ha Qt-t hasznalsz logikus valasztas lehet egy QByteArray, de lehet std::vector, amint azt mar masok is javasoltak, vagy egyszeru malloc/realloc/etc es meg egy rakas mas lehetoseg ;) A TCP socketen olvasasra varo adat lekerdezheto, ha nem akarod addig atvenni az adatot amig nincs ertelme raengedni a parsert (ettol persze az adat meg atveheto), pl. egy ioctl(fd, FIONREAD, &bytes) megteszi, de ez rendszerfuggo. En a dinamikus buffert preferalom, min-max ertekkel kombinalva, de van amikor erre nincs lehetoseg mert szukosek az eroforrasok. Na ja, szegeny ember vizel es foz ;)

> melyikkel lehet a leghatékonyabban olvasni egy olyan TCP stream-et

Én stdio-val csináltatnám a blokkonkénti beolvasást, és karakterenként dolgoznám fel a szöveget. Egyszerűbb kód, kevesebb hiba, hatékonyabb programozó.

Nagyon köszönöm mindenkinek a tanácsát, ötletét.
Mivel több session is csatlakozhat, így mindenképpen kell select() vagy társai.
Szerintem mindenképpen csinálok egy olvasási buffert, majd egy előfeldolgozót, ami már a teljes adatot tud adni majd a feldolgozónak. Így az egyes session-nek elég külön külön buffer, és elég csak egy olvasási buffer. Ezzel talán egyszerűbb kezelni az egyéb TCP dolgokat. Bár még lehet, hogy thead-re bontom az egyes session kezeléseket, ezzel minden egyszerűbb lenne.