Do spisu tresci tematu 9

9.2.4 RPC - Serwer TCP i UDP




Spis tresci


Wprowadzenie

Zasada dzialania zdalnego wywolywania procedur (RPC) jest opisana we wstepie. Sam serwer czeka w nieskonczonej petli, realizujac wszystkie nadchodzace zadania klientow RPC. Oprocz tego musi on rajestrowac nowo udostepniane programy w lokalnym procesie odwzorowywania portow portmapperze.
Kazdy program (zbior procedur) udostepniany klientom RPC przez serwera jest opisany struktura SVCXPRT tzw. manipulatorem transportowym. Zaleznie od uzywanego protokolu transmisji ma ona inny wyglad. RPC moze uzywac protokolow: TCP/IP - strumieniowego (polaczeniowego) i datagramowego (bezpolaczeniowego) lub UDP/IP - datagramowego. Serwer moze tez nie uzywac zadnego protokolu i dzialac dla lokalnych klientow, ale ta metoda jest stosowana tylko do testow, wiec nie ma sensu jej omawiac.
Struktura opisujaca manipulator transportowy w uniwersalnej postaci:

typedef struct {
	int xp_sock;		
	u_short xp_port;
	struct xp_ops {
		bool_t (*xp_recv)();
	    	enum xprt_stat (*xp_stat)();
	    	bool_t (*xp_getargs)();
	    	bool_t (*xp_reply)();
	    	bool_t (*xp_freeargs)();
	    	void   (*xp_destroy)();
	} *xp_ops;
	int xp_addrlen;
	struct sockaddr_in xp_raddr;
	struct opaque_auth xp_verf;
	caddr_t xp_p1;
	caddr_t xp_p2;
} SVCXPRT;
Znaczenie pol jest nastepujace:
xp_sock - deskryptor gniazda przez ktore serwer bedzie odbieral zadania dla tego programu;
xp_port - numer portu przyporzadkowany do programu, w przypadku protokolu polaczeniowego jest on rowny 0;
xp_ops - operacje zdefiniowane na manipulatorze, ich implementacja zalezy od protokolu i jego rodzaju w przypadku TCP. Realizuja one kolejno: odbieranie nadchodzacych fzadan, pobranie statusu lacza, pobranie argumentow dla zdalnej procedury, wyslanie wynikow zdalnej procedury, zwalnianie pamieci zaalokowanej na argumenty i niszczenie tej struktury (xp_ops);
xp_addrlen, xp_raddr - adres zdalny i jego dlugosc;
xp_verf - dane do sprawdzenia autentycznosci zadan;
xp_p1, xp_p2 - pola prywatne zalezne od protokolu.

Procedura uaktywniajaca manipulator transportowy:
void xprt_register()
parametr:
manipulator transportowy
dzialanie:
Pobiera numer gniazda z manipulatora i ustawia bit o tym numerze w masce aktywnych gniazd (maska jest uzywana przez funkcje select() do rozpoznania z ktorych gniazd mozna pobierac zadania). Do dezaktywacji manipulatora sluzy procedura xprt_unregister().


Rejestracja uslugi

Zarejestrowane programy, udostepniane przez serwer, sa trzymane na liscie wywolan svc_callout. Kazdy element listy zawiera zbior procedur udostepnionych w ramach danego programu:

struct svc_callout {
	struct svc_callout *sc_next;
	u_long sc_prog;
	u_long sc_vers;
	void (*sc_dispatch)();
};
sc_prog - numer programu;
sc_vers - wersja programu;
sc_dispatch - funkcja lacznika serwera ( pobiera argumenty i wywoluje udostepniana funkcje RPC);

Funkcja rejestrujaca usluge w serwerze i ewentualnie udostepniajaca ja klientom przez portmappera:
svc_register()
parametry:
warstwa transportowa, numer i wersja programu, rejestrowana procedura lacznika oraz typ protokolu (TCP, UDP)
dzialanie:
Sprawdza, czy juz zostal zarejestrowany ten program/wersja w liscie callout, jesli nie, to dopisuje nowy program do listy. Jesli protokol to: TCP lub UDP, to rejestruje nowy program i wersje w portmapperze funkcja pmap_set(). Do usuwania zarejestrowanych na liscie wywolan (svc_callout) programow sluzy procedura svc_unregister().


Obsluga zadan

Fukcja odpowiedzialna za ciagle przyjmowanie zlecen od klientow i wywolywanie odpowiednich procedur:
void svc_run()
dzialanie:
Funkcja dziala w nieskonczonej petli. Czeka na wywolanie udostepnionej procedury RPC. Uzywa funkcji select() czytajacej z uzywanych (zarejestrowanych przez xprt_register()) gniazd. W przypadku odczytania zadania z interesujacego nas gniazda, uruchamiana jest funkcja odbierajaca to zadanie svc_getreqset(). Jedynym (no może nie do końca) sposobem zatrzymania serwera jest funkcja svc_exit().

Funkcja odbierajaca zadania RPC:
void svc_getreqset()
parametry:
maska uzywanych gniazd
dzialanie:
Dla wszystkich uzywanych gniazd sprawdza, czy jest w nich zadanie. Jesli juz takie gniazdo znajdzie, to odbiera zadanie sposobem zaleznym od protokolu (funkcja zdefiniowana w manipulatorze tranportowym). Sprawdza autentycznosc danych. Szuka programu i wersji w liscie wywolan callout. Uruchamia funkcje lacznika serwera, zapisana w znalezionym elemencie listy. Na koniec, jesli cos sie nie udalo, generuje status wywolania i uruchamia ew. jedna z funkcji opisujacych blad: brak programu, wersji programu lub wywolywanej procedury, blad przy dekodowaniu argumentow albo sprawdzaniu ich autentycznosci.

Funkcja odpowiadajaca na zadanie klienta i wysylajaca mu wyniki (nawet, jesli funkcja zdalna jest typu void) jesli procedura RPC zostala prawidlowo wykonana:
bool_t svc_sendreply()
parametry:
manipulator transportowy, wyniki zwrocone przez procedure RPC
dzialanie:
Tworzy strukture rpc_msg, zapisuje do niej wyniki (jesli wywolana zostala funkcja typu void, to zwraca XDR_VOID) i wysyla ja funkcja zdefiniowana w manipulatorze.


Serwer TCP

Istnieja dwa rodzaje serwerow TCP:

  1. bezpolaczeniowy do nawiazania kontaktu z klientem;
  2. polaczeniowy (strumieniowy) do przesylania strumieniem danych, miedzy serwerem a klientem. Polaczenie jest utrzymywane az do zakonczenia wywolywania procedury zdalnej.

Serwer bezpolaczeniowy TCP

W serwerze bezpolaczeniowym TCP struktura prywatna manipulatora transportowego xp_p1 zawiera tylko wielkosci buforow wejscia i wyjscia dla gniazda, pole xp_p2 nie jest uzywane. Wyglada to tak:

struct tcp_rendezvous {
	u_int sendsize;
	u_int recvsize;
};
Funkcja tworzaca serwera bezpolaczeniowego, opartego na protokole TCP:

SVCXPRT *svctcp_create()
parametry:
numer gniazda, wielkosc buforow dla odczytu i zapisu do gniazda
wynik:
zwraca manipulator transportowy
dzialanie:
Jesli numer gniazda jest rowny RPC_ANYSOCK, to tworzone jest gniazdo przy pomocy socket(). Gniazdo jest zwiazywane z wolnym portem (bind()), jesli dotychczas nie bylo. Wtedy na tym gniezdzie wywolywana jest funkcja listen(). Jesli wszystko zadzialalo poprawnie, to tworzona jest struktura manipulatora transportowego SVCXPRT i wpisywane sa do odpowiednich pol: struktura prywatna z wielkosciami buforow we/wy, powiazane port i gniazdo, wektor operacji xp_ops wykonywalnych na manipulatorze. Nastepnie manipulator jest rejestrowany xprt_register().

Funkcje obslugi serwera nalezace do pola xp_ops manipulatora transportowego uzywajacego protokolu TCP/IP w trybie bezpolaczeniowym:

static struct xp_ops svctcp_rendezvous_op = {
	rendezvous_request,
	rendezvous_stat,
	(bool_t (*)()) abort,
	(bool_t (*)()) abort,
	(bool_t (*)()) abort,
	svctcp_destroy
};
Funkcja obslugujaca odbieranie zadan RPC:
bool_t rendezvous_request()
dzialanie:
Na poczatku wywolywana jest funkcja systemowa accept(). W przypadku nawiazania polaczenia z klientem, tworzony jest nowy manipulator - strumieniowy w miejsce starego bezpolaczeniowego. Zwraca zawsze FALSE, bo nigdy nie ma informacji do przetworzenia.

Funkcja zwracajaca status manipulatora:
enum xprt_stat rendezvous_stat()
dzialanie:
Zawsze zwraca gotowosc XPRT_IDLE.

void svctcp_destroy()
dzialanie:
Odrejestrowuje manipulator xprt_unregister(), zamyka gniazdo (close()) i zwalnia pamiec.

Pozostale funkcje w przypadku TCP bezpolaczeniowego sa puste.

Serwer polaczeniowy TCP

W kliencie polaczeniowym TCP struktura prywatna manipulatora transportowego xp_p1 zawiera tylko wielkosci buforow wejscia i wyjscia dla gniazda. Wysylania to tak:

struct tcp_conn
	/* status lacza */
	enum xprt_stat strm_stat;
	/* identyfikator pakietu */
	u_long x_id;
	/* potok komunikatow XDR */
	XDR xdrs;
	/* identyfikacja poprawnosci danych */
	char verf_body[MAX_AUTH_BYTES];
};
Funkcja tworzaca serwera, opartego na protokole strumieniwym dziala podobnie jak ta na protokole bezpolaczeniowym TCP. Roznica polega na pobraniu dowolnego OTWARTEGO deskryptora pliku jako gniazdo:

SVCXPRT *svcfd_create() lub SVCXPRT *makefd_xprt()
parametry:
numer gniazda, wielkosc buforow dla odczytu i zapisu do gniazda
wynik:
zwraca manipulator transportowy
dzialanie:
Tworzona jest struktura manipulatora transportowego SVCXPRT i wpisywane sa do odpowiednich pol: zarejestrowy przy pomocy funkcji xdr_reccreate() potok XDR, status na XPRT_IDLE, port ustawiony jest na 0, bo jest to polaczenie strumieniowe, powiazane gniazdo, wektor operacji xp_ops wykonywalnych na manipulatorze (inny niz w wersji bezpolaczeniowej). Nastepnie manipulator jest rejestrowany xprt_register().

Funkcje obslugi serwera nalezace do pola xp_ops manipulatora transportowego uzywajacego protokolu TCP/IP w trybie polaczeniowym:

static struct xp_ops svctcp_op = {
	svctcp_recv,
	svctcp_stat,
	svctcp_getargs,
	svctcp_reply,
	svctcp_freeargs,
	svctcp_destroy
};
Funkcja obslugujaca odbieranie zadan RPC:
bool_t rendezvous_request()
dzialanie:
W przypadku nawiazania polaczenia z klientem - funkcja systemowa accept(), tworzony jest nowy manipulator - strumieniowy w miejsce starego datagramowego. Zwraca zawsze FALSE, bo nigdy nie ma informacji do przetworzenia.

Funkcja zwracajaca status manipulatora:
enum xprt_stat svctcp_stat()
dzialanie:
Zwraca zaleznie od statusu polaczenia:
XPRT_DIED - kiedy polaczenie zostalo zerwane
XPRT_MOREREQS - kiedy sa jeszcze zadania w strumieniu
XPRT_IDLE - kiedy jest gotowy

bool_t svctcp_recv()
parametry:
manipulator transportowy i wskaznik do struktury typu rpc_msg
wynik:
na strukture rpc_msg wpisuje zdekodowana informacje i zwraca TRUE/FALSE w przypadku powodzenia/bledu
dzialanie:
Przechodzi do nastepnego rekordu w potoku xdrrec_skiprecord() a nastepnie dekoduje rekord na strukture rpc_msg funkcja xdr_callmsg.

Funkcja zwracajaca wyniki zdalnej procedury klientowi:
bool_t svctcp_reply()
parametry:
manipulator transportowy i wskaznik do struktury typu rpc_msg
wynik:
na strukture rpc_msg wpisuje zakodowana informacje i zwraca TRUE/FALSE w przypadku powodzenia/bledu
Funkcja niszczaca strukture xp_ops dziala tek samo jak w przypadku bezpolaczeniowym a inne funkcje nie sa ciekawe.

Funkcja czytajaca z gniazda TCP:
int readtcp()
parametry:
manipulator transportowy, bufor i jego wielkosc
wyniki:
ilosc przeczytanych bajtow lub -1 w przypadku bledu
dzialanie:
Sprawdza istnienie polaczenia select(). Nastepnie wywoluje funkcje systemowa read() i zwraca liczbe przeczytanych bajtow. Jesli liczba przeczytanych bajtow <= 0 lub w przypadku innego bledu zamyka lacze i zwraca -1.

Serwer UDP

Serwer protokolu UDP stosuje komunikacje bezpolaczeniowa z klientami. Serwer RPC oparty na protokole UDP/IP wykorzystuje pole prywatne manipulatora transportowego xp_p1 na bufor. Dodatkowo ma zdefiniwana nastepujaca strukture prywatna w polu xp_p2 :

struct svcudp_data {
	/* wielkosc bufora dla operacji we/wy */
	u_int   su_iosz;
	/* identyfikator pakietu */
	u_long	su_xid;
	/* potok komunikatow */
	XDR	su_xdrs;
	/* identyfikacja poprawnosci danych */
	char	su_verfbody[MAX_AUTH_BYTES];
	/* cashe pamietajacy wywolywane procedury */
	/* i zapewniajacy poprawnosc semantyk wywolania dokladnie-raz */
	char * 	su_cache;
};
Operacje zdefiniowane w polu xp_ops w przypadku UDP wygladaja tak:
static struct xp_ops svcudp_op = {
	svcudp_recv,
	svcudp_stat,
	svcudp_getargs,
	svcudp_reply,
	svcudp_freeargs,
	svcudp_destroy
};

Funkcja tworzaca serwer:
SVCXPRT *svcudp_create()
parametry:
deskryptor gniazda
wyniki:
Zwraca zarejestrowany manipulator transportowy
dzialanie:
Wywoluje funkcje svcudp_bufcreate(), podajac jej dodatkowo wielkosci buforow we/wy zdefiniowane przez stala MSGUDPSIZE=8800. Dalej dziala tak samo jak svctcp_create(). Wielkosc bufora w polu xp_p1 ustala na MAX(buforwe, buforwy).

Funkcja odbierajaca zadania klientow w UDP:
bool_t svcudp_recv()
parametry:
manipulator transportowy i struktura rpc_msg
wyniki:
na strukture rpc_msg wpisuje zdekodowana informacje i zwraca TRUE/FALSE w przypadku powodzenia/bledu
dzialanie:
Odbiera dane z gniazda funkcja recvfrom() i zapisuje je do bufora, czyli do pola xp_p1. Odebrane dane musza miec dlugosc co najmniej 4*sizeof(u_long). Nastepnie dokonuje zdekodowania danych w buforze. Zapisuje identyfikator pakietu w su_xid. Sprawdza, czy w cashe'u ma zapisane wyniki wywolania tej procedury zdalnej i wtedy od razu wysyla odpowiedz sendto()

Funkcja wysylajaca wyniki procedury RPC:
bool_t svcudp_reply(xprt, msg)
parametry:
xprt - manipulator transportowy, msg - struktura rpc_msg
wyniki:
zwraca TRUE/FALSE w przypadku powodzenia/bledu
dzialanie:
Koduje wyniki ze struktury rpc_msg. Wysyla je do klienta sendto(). Wpisuje wyniki (zakodowane) do cashe'u w polu xp_p1.


Bibliografia

  1. Biblioteka LIBC: ./rpc/* - implementacja RPC
  2. Pliki naglowkowe: /usr/include/rpc/*
  3. R. Stevens: ,,Programowanie zastosowan sieciowych'' - ogolne wiadomosci o RPC


Autor: Klaudiusz Zych