Hálózati programozásban szeretnék segítséget

 ( Jester_Racer | 2009. június 9., kedd - 14:37 )

Üdv mindenkinek!

Egy kis chat programot kezdtem el írni C++ nyelven azonban most elakadtam benne, azt hiszem valamit rosszul csinálok a hálózati programozás résznél.
Ezért írtam egy kis példa szervert meg klienst(kiszedtem belőle a szálkezelést meg a többi felesleges dolgot), hogy rájöjjek mi a gond de így se sikerült.
Feldobtam a kódot pastebinre:
szerver
kliens
tcpSocket osztály

(A tcpSocket osztályt az egyszerűség kedvéért egyben töltöttem fel a pastebinre, amúgy természetesen két külön fájlban van :)
Az az érdekes hogy ha elindítom a szervert és utána a klienst, akkor a kliens csatlakozik a szerverhez de az üdvözlő üzenetet már nem kapja meg(próbáltam a szervert egy "sleep 5" paranccsal késleltetni, hátha ez a gond, de nem), viszont ha a szerver elindítása után
telnet localhost 11111
paranccsal csatlakozok a szerverhez akkor tökéletesen működik. Bármit begépelek a kliensen, a szerver visszaküldi azt nekem.
Valaki meg tudja mondani mit csinálok rosszul? :)

Köszi!

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

A szerver az üdvözlőszövegben nem küld entert, a kliens viszont addig olvas, amíg nem jön egy enter.

ÓÓÓÓ a fene egye meg. Agyon kerestem mi lehet a hiba a hálózatkezelésben....
Köszönöm a segítségedet, ezt a kis hibát fél napja nem sikerült észrevennem, pedig debuggoltam is meg minden... :)

Az baj, ha tovabbfejlesztesi celzattal lenyulnam a tcpSocket kodjat? Mar reg keresek valami egyszeru, jol ertheto kodot...
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Ezt is nézd meg, én elég sokféle dologra használom,
saját kód, használd egészséggel (-::

protokol: ftp
domain: meditor.hu
user: public

cd ./c_examples

és itt: Mediag.tar.gz

> Sol omnibus lucet.

Halas koszonet.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Használd egészséggel ha tetszik ;)

Egész letisztult a tcpSocket osztály, de talán elfogadsz pár építő jellegű kritikát...

Ha jól látom van saját Exception osztályod. Ez felesleges, létezik szabványos exception osztály, sőt több is:
http://www.cppreference.com/wiki/exception/start

Exception helyett nyugodtan használhatnád a runtime_error-t, Network_err helyett meg csinálhatsz network_error-t, ami lehet runtime_error leszármazott...

A Send, és Recv osztályok protokollja nem túl szerencsés. Úgy szokták ezt csinálni, hogy átküldöd az üzenet méretét (mondjuk unsigned int) majd az üzenetet.
Így fogadó oldalon sem kell byte-onként olvasnod, a fórum indító hiba sosem lett volna gond, illetve nem csak string-ekkel fog később működni.

A kódod lefagy, ha Recv vagy Send közben megszakad a kapcsolat. Sőt, sehol nem látok ilyen jellegű ellenőrzést, úgyhogy valószínűleg akkor is lefagy vagy elszáll, ha bármikor megszakad a kapcsolat.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Az utolsot hogy lehet ellenorizni?
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Szerintem csak recv és send híváskor.

Winen ha bármilyen error van, akkor SOCKET_ERROR-ral térnek vissza. Illetve a recv 0-val, ha nem megszakadt, hanem rendesen lezárták a kapcsolatot:
http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx

Gondolom Linuxon is ugyanez a helyzet maximum mások a hibakódok...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Kosz.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Építő jellegű kritikákat mindig szivesen fogadok! :)
Köszi a tanácsokat, épp most kezdtem el átírni a Send() és a Recv() függvényeket ahogy mondtad, aztán következik a kivételkezelés.

Kérdésem is lenne akkor már: egy ilyen jellegű program esetén hogy a legcélszerűbb ellenőrizni, hogy egy kliens továbbra is kapcsolódik-e a szerverhez? (pl. ha elhalálozik az egyik kliens akkor a szerver addig nem fogja megtudni, hogy kapcsolódik-e még, amíg nem próbál neki üzenetet küldeni)
Gondoltam olyasmire, hogy mondjuk félpercenként küld a szerver minden kliensnek egy üzenetet amire mondjuk egy "OK" választ vár, amelyik klienstől meg nem kap, azt a socketet bezárja, csak nem tudom ez mennyire "szép" megoldás.

Szerintem ezt így szokták, de nem vagyok egy hálózat guru...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Az egyik megoldás az, amit te is felvázoltál. Előnye az, hogy tetszőlegesre állíthatod a timeout-ot.

A másik megoldás pedig:

setsockopt(..., SO_KEEPALIVE, ...)
man 7 socket
man 2 setsockopt

A poll(); jelzi a szakadást. A SIG_PIPE-ot fel kell húzni.
> Sol omnibus lucet.

Köszi mindkettőtök válaszát, de közben sikerült egyszerűen a recv() == 0 figyelésével megoldanom a dolgot, ahogy azt fentebb is írták.

A recv() az akkor tér vissza 0-val, ha a túloldal rendesen bezárta a kapcsolatot. Ha egy TCP kapcsolaton keresztül nem forgalmazol semmilyen adatot, akkor szerintem egy socketről nem tudod eldönteni, hogy "él"-e, vagy sem.

SIGPIPE szignált akkor kapsz, amikor írni probálsz egy socketre, de nem sikerül, mert az közben megszakadt, és az írás pillanatában derül ki, hogy megszakadt.

Szóval ha csak a recv() visszatérési értékét nézed, akkor adott esetben csak jóval később fogod tudni érzékelni, hogy a kapcsolat megszakadt-e. Ennek az elkerülésére kell bevezetni mondjuk a ping/pong üzeneteket, mint ahogy az irc-ben csinálják, vagy az általam korábban felvázolt SO_KEEPALIVE opciót kell megadni a socketnek.

Nos, az én esetemben az van, hogy a kliens egy külön szálon folyamatosan recv()-el és ha 0 a recv() visszatérési értéke akkor kivételt dob és kilép.
A szerver esetében hasonló a helyzet, minden kliensnek indít egy külön szálat ami folyamatosan recv()-el és ha 0 akkor kilépteti az adott klienst.

Ha kipróbálom és elindítok egy szervert meg pár klienst és az egy klienst bezárom CTRL-C lenyomásával, a szerver azonnal küldi az üzenetet a többi kliensnek hogy XY kijelentkezett. Nekem úgytűnik ez így teljesen jól működik.
Egy olyan program esetében ami csak időnként küld/vár üzenetet érthető, hogy miért nem jó ez a megoldás, az én esetemben viszont szerintem nem lenne értelme átírni a kódot úgy ahogy mondod. Vagy igen?

A Ctrl-C ha úgy vesszük egy rendes bezárás, hiszen a háttérben az operációs rendszer bezárja a kapcsolatot, amit a túloldali szerver érzékelni fog.

De mondjuk próbáld ki azt, hogy két gépet használsz, az egyikről mész a másikra, és kihúzod a köztük lévő kábelt (vagy lehúzod az interface-t). Akkor hány perc múlva fogod érzékelni, hogy bezáródott a kapcsolat? Azt te döntsd el, hogy megéri-e neked erre az esetre is felkészíteni a programot, vagy sem.

Értem, köszi, erre a lehetőségre nem gondoltam.

Igen, a ctrl-c -nek megvan ez a kellemetes/kellemetlen tulajdonsaga, hogy rendes bezarasnak minosul. Meg a kill -9 sem eletbiztositas a megszakadas tesztelesere. A kabelkitepkedes a tuti fix.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

hogy átküldöd az üzenet méretét (mondjuk unsigned int) majd az üzenetet.

+ Network byte orderben, ahogy illik.

---
pontscho / fresh!mindworkz

Ezt kifejtenéd kicsit bővebben?
Minden egyes üzenetet network byte orderben kellene elküldeni?
És ha igen, miért? Tapasztalatom szerint működik anélkül is.

Mert meg nem programoztal multiplatform kornyezetben.

---
pontscho / fresh!mindworkz

A lényeg a következő: TCP kapcsolatot használsz, aminek az a jellemzője, hogy nem tudod, hogy az üzenetnek hol vannak a határai, tehát ha írsz két 10 bájtos üzenetet, akkor azt a túloldal kiolvashatja 20bájtként is, de kiolvashatja 12+8bájtként is, stb...

Tehát meg kell oldanod, hogy minden üzenethez egy fejlécet csatolsz, amibe beírod azt is, hogy milyen hosszú az üzenet. Ezt nyilván megteheted többféleképpen is: 10-es számrendszerben szövegesen, vagy ennél az elterjettebb megoldás, hogy csinálsz egy fejlécet, amibe felveszel egy egész számot, ami az üzenet hosszát hordozza:


typedef struct {
int length;
int type; // esetleg az uzenet tipusa
} header_t;

És ekkor minden üzenet elé berakod ezt a kis fejlécet. Ennek a hátrányai:
- azt int típusa nem fix méretű, van ahol 32 bit, van ahol 16 bit. -> megoldás: uint32_t length; típus használata (stdint.h), ami minden gépen ugyannyi.
- az int bájt sorrendje nem rögzített. Pl length = 0x01, akkor egy 32 bites little-endian gépen (i386) ez 0x01, 0x00, 0x00, 0x00 formában megy át, egy big-endian gépen (sparc asszem) pedig 0x00, 0x00, 0x00, 0x01 formában. Ezért van a socket programozásnál a htons (16bit), htonl(32bit)-es függvények, de a /usr/include/endian.h-ban találsz egy rakatot.

Szóval egy platform független alkalmazás esetében az egész számok méretét nem árt rögzíteni, továbbá a bájt sorrendet. A network bájt order big-endian, a mérete nincs rögzítve, a korrektség kedvéért azt sem árt rögzíteni.

Persze ilyenkor nem árt az alábbiakra figyelni:
- ha olvasol a socketról, meg kell nézned, hogy a fejléc teljes egészében megérkezett-e, mielőtt feldolgozod a benne lévő adatot,
- ha megérkezett a fejléc, akkor meg kell nézni, hogy a utána megjött-e a hosszában közölt adat.

Es ez nem csak a halozati adat atvitelre igaz, hanem mindenhol ahol feltetelezheto, h heterogen rendszerek kozott kell adatot mozgatni.

---
pontscho / fresh!mindworkz

Így már érthető, köszi! :)

Egyébként ha valakinek esetleg vannak jó doksiai hálózati programozással kapcsolatban, azt megköszönném ha linkelné :)
Eddig az egyetlen jó amit találtam az a Beej's Guide to Network Programming. Ez az alapokat jól leírja szerintem, de komolyabb dolgokat annyira nem részletez.

Ebből érdemesebb a mindig friss angol eredetit olvasni. A magyar fordítás régi, Beej átdolgozta az anyagot rendesen már.

Köszönet az infóért, ezt észre se vettem :P