Radomir Mladenovic, student Elektronskog fakulteta u Nisu
Boban Nikolic, Ei Sigraf

OBJEKTNO ORIJENTISANI PRISTUP TCP/IP SOCKET-IMA

Uvod
Klasa CSocket
Klijent-server aplikacije i problemi
Klasa CCSocket
Klasa CData za upravljanje memorijom
Prosirenja i interfejs za ne C++ prevodioce
Zakljucak


Uvod

Svedoci smo da poslednjih nekoliko godina dolazi do sirenja i medjusobnog povezivanja racunarskih mreza, i pojave velikog broja servisa i aplikacija ciji je cilj da sto bolje iskoriste resurse umrezenih racunara i unaprede i popularisu ovakav nacin komunikacije, rada i informisanja., ili, jednostavno zabave. Ovakav, sve brzi razvoj hardverskih resursa zahteva i pracenje odgovarajucom softverskom podrskom, sto uslovljava i unapredjenje razvojnih paketa i biblioteka funkcija koji se bave mrezama. CSock biblioteka sadrzi skup klasa za C++ i znatno olaksava razvoj programa koji imaju potrebu za razmenom podataka na mrezi, sa ili bez konekcije. CSock biblioteka je razvijena za TCP/IP protokol i u Windows verziji postoji kao dinamicka biblioteka (DLL).


Klasa CSocket

Osnovni entitet pri TCP konekciji je soket. Sama njegova implementacija zavisi od operativnog sistema i krece se od identifikatora do fajl deskriptora. Soket tako mozemo da zamislimo i kao objekat, tj instancu klase u kojoj su sadrzane sve funkcije za rad sa soketom. Ova klasa mogla bi da izgleda na sledeci nacin:

class CSocket
{ protected:
SOCKET sock;
int protokol;
int status; // identifikuje stanje socketa virtual void OnRead( UINT ); // soket spreman za citanje virtual void OnWrite( UINT ); // soket spreman za pisanje virtual void OnOOB( UINT ); // out-of-band data za citanje virtual void OnAccept( UINT ); // spreman za accept nove konekcije virtual void OnConnect( UINT ); // konekcija zavrsena virtual void OnClose( UINT ); // soket zatvoren . . . public: CSocket(); ~CSocket(); // verzije za inicijalizaciju socketa virtual BOOL Init( const char *local_ini ); virtual BOOL Init( sockaddr *adr ); virtual void close(); virtual long GetStatus(); virtual int send( void *buff, int len ); virtual int recv( void *buff, int len ); virtual int io(long cmd, u_long FAR *argp); virtual int AsyncSelect( HWND hWnd, UINT msg, long event ); virtual int Socket( int af = PF_INET, int type = SOCK_STREAM, int prot = 0 ); virtual int bind( sockaddr *name, int NameLen ); virtual int bind( int Port, int fam = AF_INET ); virtual SOCKET accept(); virtual int listen( int que ); virtual SOCKET operator= ( SOCKET s ); . . . };
Ovakva klasa sadrzi clan tipa SOCKET koji ima vrednost dobijenu od sistema pri otvaranju soketa. Clan protokol ukazuje na tip protokola za koji je soket otvoren (TCP ili UDP), dok status predstavlja trenutno stanje soketa ili samog objekta. Virtuelne funkcije OnRead, OnWrite, OnClose, OnAccept i druge iz ove grupe funkcija, mogu se iskoristiti u izvedenim klasama i predefinisati tako da na odredjeni nacin reaguju na pojavu podataka za citanje, zatvaranje soketa odnosno pucanje veze, pojavu urgentnih informacija na soketu ili na zahtev za konekcijom. Ostvarivanje konekcije moguce je otvaranjem soketa funkcijom Socket i postavljanjem soketa (bind) na odredjeni port. Ako program treba da prihvati konekciju, koristi se listen za osluskivanje porta, OnAccept za detekciju zahteva, i najzad, accept za prihvatanje veze.

Ipak, u vecini slucajeva, PC se koristi kao klijent i povezuje se za racunar koji ima ulogu servera za datu aplikaciju. U takvim slucajevima, najveci komfor se postize inicijalizacijom preko Init funkcija, koje prihvataju ili sockaddr strukturu, ili ime INI fajla u kojem se nalaze stavke sa IP adresom ili imenom hosta i porta na kome on ocekuje konekciju.

Pisanje na soket, odnosno slanje podataka, obavlja se funkcijom send kojoj se daje pointer na podatke koji se salju i broj bajtova za slanje. Citanje sa soketa vrsi se funkcijom recv koja uzima pointer na bafer u koji treba smestiti podatke i broj bajtova koji treba procitati. Obe ove funkcije, sem poziva sistemskih funkcija, proveravaju i detektuju eventualne greske i vracaju status.


Klijent-server aplikacije i problemi

Klasa kao sto je CSocket sadrzi u sebi sve sto je potrebno za mreznu komunikaciju. Medjutim, u slozenim klijent-server aplikacijama dolazi do pojave problema koji bitno usloznjavaju komunikaciju. Razmena podataka izmedju klijenta i servera vrsi se po principu transakcija, odnosno zahteva, i ako je potrebno, odgovora na zahtev, pri cemu svaka vrsta zahteva ima jednistven identifikator, koji se naziva kod transakcije.

Prvi problem koji se javlja pri radu sa velikim kolicinama podataka koje se salju i citaju sa soketa, je taj da IP deli jedan veliki paket na vise manjih. Tako, ne mozemo da se oslonimo na to da cemo u jednom citanju pokupiti sve podatke koje nam je poslao server. Jedan poziv sistemske funkcije za citanje podataka sa soketa pokupice prvi paket, cija velicina zavisi od slobodne memorije bafera, koji inace kontrolise sistem. Velicina ovih najvecih paketa je 1460 bajtova (odnosno 1006 kod SLIP-a , IP na serijskim linijama). Zato se, ako ne proveravamo duzinu pri citanju, moze pokazati kao misterija cinjenica da je server poslao sve podatke, a da ih aplikacija nije primila. Drugi problem vezan sa velikim kolicinama podataka je kada je za prihvat podataka potrebno vise memorije nego sto je predvidjeno aplikacijom, pa je potrebno da se razrese i ovakve situacije, a da se podaci ipak procitaju da ne bi ostali u soketu.
Druga vrsta problema javlja se kod aplikacija koje treba da, asinhrono, znaci u bilo kom trenutku, prihvate i obrade neki zahtev ili obavestenje od strane servera. Moze se desiti da aplikacija posalje zahtev serveru i ceka odgovor, a da u tom trenutku stigne transakcija od servera koja nije ocekivana, i koja moze da bude protumacena kao odgovor na zahtev zatrazen od strane klijenta.
Zbog ovih i slicnih problema koji se javljaju pri komunikaciji, razvijeno je par klasa za upravljanje memorijom i dogovoren protokol za klijent-server aplikacije koji je ugradjen u klasu CCSocket.


Klasa CCSocket ( ClientSocket )

Klasa CCSocket nasledjuje klasu CSocket, pri cemu najveci broj njenih osobina zadrzava, a standardizuje nacin razmene transakcija na mrezi izmedju server i klijent aplikacije. Osnovne funkcije koje dodaje ova nova klasa, a koje se ticu komunikacije su:
class CCSocket : public CSocket
{
	.  .  .
   public:
	virtual void Send( UINT kod, const void *data = NULL, long len = 0 );
	virtual CData& Recv( UINT kod, int block = 1 );
	virtual CData& Reply( UINT kod, const void *data = NULL, 
		long len = 0,  int block = 1 );
	.  .  .
};
Podaci se sada salju funkcijom Send koja preuzima kod transakcije, pointer na podatke i kolicinu podataka koja se salje kroz soket. Recv kao jedini obavezan parametar zahteva kod transakcije, i opciono, da li je poziv blokiran, odnosno da li se obavezno ocekuje transakcija sa datim kodom ili se samo proverava postojanje takve transakcije u baferu. Funkcija Reply predstavlja kombinaciju Send i Recv.

Da bi se razresile konfliktne situacije, kao gore navedene, klasa CCSocket tj. objekat njenog tipa, ispred svih paketa koji se razmenjuju kroz soket dodaje zaglavlje oblika:
struct t_head
{
   char id[2];
   char kod[3];
   unsigned char arg;		//   tip onoga sto sledi u sledeca 4 bajta hedera
   union   {
   	long len;		//   duzina data dela koji sledi iza hedera
	.  .  .
   };
};
Zaglavlje zadrzi identifikator onoga ko salje, i ne mora da ima prakticni znacaj. Za njim slede kod transakcije, i identifikator tipa, koji govori o tome sta je sadrzano u nastavku zaglavlja, a u vecini slucajeva se radi o kolicini podataka koja se salje iza zaglavlja. Zaglavlje moze sadrzati i informacije drugog tipa, ne samo o duzini transakcije, na sta ukazije t_head.arg, i u kom slucaju iza zaglavlja nema podataka.
Funkcija Recv obavlja sva citanja sa soketa, pri cemu se prihvata prvo zaglavlje a zatim podaci (ako ih ima), pri cemu se razresava problem pojave transakcije koja se ne ocekuje. U tom slucaju pristigla transakcija se procita i cuva, a transakcija sa zeljenim kodom se ceka dalje. Kada naidje zaglavlje sa zeljenim kodom, rezervise se onoliko memorije koliko je potrebno za prihvat podataka. Podaci procitani sa soketa vracaju se aplikaciji kao objekti tipa CData.


Klasa CData za upravljanje memorijom

Klasa CData namenjena je za prihvatanje podataka sa soketa. Cilj je bio da aplikacija bude oslobodjena rezervisanja i oslobadjanja memorije za prihvat transakcije. Objekat tipa CData, izmedju ostalog, sadrzi pointer na bafer sa podacima i velicinu tog bafera. Kada se kreira, ovakav objekat ne raspolaze rezervisanom memorijom. Memoriju rezervise funkcija Recv i dodeljuje je objektu CData. Kada se prihvati sav sadrzaj transakcije, Recv kao rezultat vraca ovaj objekat. Prihvat podataka u aplikaciji i koriscenje podataka se vrsi sa:
	CData data;
	data = soc.Recv( 100 );
	char *str = (char *) data.GetData();
	.  .  .
Podaci koje drzi objekat data, tj. na koje ukazuje pointer str, mogu se koristiti sve dok zivi dati objekat ili dok se ovom objektu na dodele novi podaci (npr. ponovnim citanjem sa soketa). U tim slucajevima oslobadja se zauzeta memorija.


Prosirenja i interfejs za ne C++ prevodioce

Za aplikacije u C++-u, najelegantniji nacin za dodefinisanje ponasanja objekata za sokete je nasledjivanje jedne od klasa iz biblioteke i prilagodjenje funkcija specificnostima aplikacije ili protokola. Na ovaj nacin omoguceno je stvaranje soketa koji mogu da obave deo transakcija zatrazenih od strane servera bez obavestavanja aplikacije, i slicno. Za ne C++ prevodioce, pristup klasama iz CSock biblioteke moguc je kroz eksportovane funkcije DLL-a, kao sto su:
	int CSInit( const char *local_ini );
	void CSClose( int ids );
	int CSsend( int sid, char *buff, int len );
	int CSrecv( int sid, char *buff, int len );
	int CSSend( int sid, int kod, , char *buff, int len );
	int CSRecv( int sid, int kod );	// vraca ID bafera u koji je prihvacena transakcija
	int CSGetLength( int did );	// vraca velicinu bafera
	int CSGetData( int did, char *buff );	// kopiranje bafera
	.   .   .	
U ovim funkcijama soketu se pristupa preko celobrojnog identifikatora, kako bi se ova biblioteka, bez problema, mogla koristiti i u jezicima kao sto je Visual Basic. Isto tako, identifikator se dobija i za CData objekte. Ovakvim funkcijama omoguceno je iskoriscenje mogucnosti klasa iz biblioteke ali je njihovo koriscenje ipak znatno neudobnije.


Zakljucak

Prednost razvoja programa uz koriscenje CSock biblioteke, sem konfora koji pruza u radu sa mrezom, je i to sto ne postoji direktna zavisnost izmedju samog protokola na mrezi i aplikacije. DLL predstavlja, na neki nacin, interfejs za izlazak programa na mrezu, tako da je u slucaju potrebe za nekim drugim mreznim protokolom dovoljna zamena DLL-a bez intervencije na kodu programa. Iako se moze uciniti da je ovakvo usloznjavanje nepotrebno i da je zbog objektno orjentisane arhitekture rad sporiji nego sto bi, u teoriji, mogao da bude, u praksi je taj gubitak, kada se uzme u obzir brzina mreze, sasvim zanemarljiv. Sa druge strane, nasledjivanje klasa predstavlja dobru osnovu za razradu postojeceg protokola namenjenog klijent-server okruzenju, ili za izgradjivanje specificnih protokola od strane programera koji razvija aplikaciju.