RPC w Linuxie nie jest jednak realizowane przez jadro systemu, a przez standardowa biblioteke LIBC (w jadrze jest zaimplementowany tylko fragment klienta RPC przy okazji systemu NFS). Jak sama nazwa wskazuje, RPC sluzy do zdalnego wywolywania procedur, a dziala w nastepujacy sposob:
Procedury zdalne sa identyfikowane przez trzy liczby: numer programu, numer wersji programu i numer procedury. Pierwsze dwa sluza do otrzymania od portmappera numeru portu (TCP albo UDP) na komputerze serwera. Portmapper jest procesem dzialajacym na komputerze serwera i sluzacym do informowania o numerach portow serwerow RPC; nie jest on odpowiedzialny za ich przydzielanie (patrz: portmapper). Numery procedur identyfikuja procedury w serwerze.
Jako ze RPC moze uzywac dwoch odmiennych protokolow: TCP albo UDP, istnieja dwa typy klientow i warstw transportowych serwera, dzialajacych na troche innych zasadach. Warstwa transportowa posredniczy miedzy pojedynczym gniazdem a serwerem. Serwer RPC moze obslugiwac w jednej chwili kilka warstw transportowych, po jednej na: kazde aktywne polaczenie TCP, na kazde oczekiwanie polaczenia TCP (gniazdo po wywolaniu listen()
)
oraz na kazde gniazdo UDP. Wiecej o serwerze: patrz serwer.
TCP
W tym protokole jest zapewniona kontrola polaczenia (tzn.: protokol martwi sie o to czy wiadomosci dojda do adresata i czy bedzie to w odpowiedniej kolejnosci). Zatem jesli klient wysle raz komunikat do serwera, to ma pewnosc, ze zostanie on dostarczony (o ile nie zerwie sie polaczenie). Klient RPC na poczatku korzystania z procedur RPC tworzy polaczenie z serwerem, ktore jest utrzymywane az do konca wywolywania procedur RPC, a zamykane jest dopiero przy usuwaniu struktur klienta. W przypadku TCP powstaje problem w jaki sposob rozrozniac oddzielne komunikaty (jeden komunikat to zestaw parametrow jednej procedury lub jej rezultat), gdyz gniazdo TCP dziala na zasadzie strumienia. Sytuacje ratuje pewien typ potoku XDR, ktory umozliwia podzial danych na oddzielne rekordy (patrz: XDR). Dla protokolu TCP istnieja dwie warstwy transportowe serwera: jedna odpowiada za nawiazywanie polaczen, zas druga jest uzywana po polaczeniu z klientem.
UDP
Protokol ten umozliwia przesylanie datagramow czyli oddzielnych pakietow danych i nie gwarantuje dostarczenia danych do adresata, ani ich odpowiedniej kolejnosci u adresata. Miedzy komputerami nie nawiazuje sie polaczenia. Na poczatku dzialania klient tworzy gniazdo, ktorym bedzie wysylac i odbierac oddzielne komunikaty. Przy kazdym wywolaniu procedury klient wysyla wiadomosc do serwera i oczekuje na odpowiedz. Jesli ta po okreslonym czasie nie nadchodzi, to klient probuje wyslac wiadomosc ponownie. Jesli po kilku probach klient nie otrzymuje wiadomosci zwrotnej to zwraca blad o niemoznosci komunikacji z serwerem. Dla protokolu UDP serwer moze stosowac buforowanie (cache) wynikow procedur, dzieki czemu nie trzeba wywolywac kilka razy tych samych procedur z tymi samymi parametrami (jesli odpowiedzi serwera nie zawsze dochodza do klienta). W przypadku UDP moze sie pojawic problem w momencie, gdy klient wysle dwa wywolania procedury i oba zostana odebrane przez serwer i przetworzone. Aby temu zarazic mozna wprowadzic numerowanie pakietow przez klienta i sprawdzanie przez serwer, czy juz wiadomosci o tym numerze nie przetworzyl (taki mechanizm jest zastosowany np. w systemie NFS).
Potoki XDR i filtry XDR
Potok XDR to ciag bajtow, w ktorym dane sa reprezentowane w formacie XDR. Proces wysylajacy / piszacy dane po zakodowaniu danych umieszcza je w potoku, zas odbiorca odbiera z potoku i rozkodowuje. Sa trzy typy potokow: potoki na plikach (dokladniej na ich dekryptorach; nie uzywane przy RPC), potoki w pamieci (na buforach; uzywane przez RPC z UDP) oraz potoki komunikatow (dzielace dane na rekordy; uzywane przez RPC z TCP).
Filtr XDR jest procedura kodujaca i dekodujaca pewien typ danych.
Potok XDR jest identyfikowany przez strukture XDR
:
enum xdr_op { XDR_ENCODE=0, XDR_DECODE=1, XDR_FREE=2 }; typedef struct { enum xdr_op x_op; struct xdr_ops { bool_t (*x_getlong)(); bool_t (*x_putlong)(); bool_t (*x_getbytes)(); bool_t (*x_putbytes)(); u_int (*x_getpostn)(); bool_t (*x_setpostn)(); long * (*x_inline)(); void (*x_destroy)(); } *x_ops; caddr_t x_public; caddr_t x_private; caddr_t x_base; int x_handy; } XDR;
Pole x_op
zawiera informacje jaka czynnosc jest w danej chwili wykonywana na potoku: kodowanie, dekodowanie, czy zwalnianie pamieci. Pole x_ops
zawiera wskazniki na funkcje, ktore mozna wykonywac na potoku (z tych operacji korzystaja na najnizszym poziomie filtry). Funkcje te przekodowuja dane. Pozostale pola sa uzywane przez rozne potoki w rozny sposob do przechowywania danych pomocniczych (deskryptory plikow, adresy w pamieci, pozycje w potoku, itp.).
Typy: bool_t
to int
, caddr_t
to char*
.
x_getlong()
x_putlong()
x_getbytes()
x_putbytes()
x_getpostn()
x_setpostn()
x_inline()
x_destroy()
Dla danego potoku nie zawsze wszystkie operacje beda dzialac, np. x_inline()
dla potoku zwiazanego z plikiem zawsze zwraca NULL
.
void xdrmem_create( XDR *xdrs, caddr_t addr, u_int size, enum xdr_op op)
Parametry:
addr
- wskaznik na przygotowany bufor
size
- jego rozmiar
op
- operacja na potoku.
Dodatkowe dane:
x_private
- wskaznik na aktualne polozenie w buforze,
x_base
- poczatek bufora,
x_handy
- rozmiar wolnej pamieci
Tworzenie:
void xdrrec_create(XDR *xdrs, u_int sendsize, u_int recvsize, caddr_t tcp_handle, int (*readit)(), int (*writeit)())
sendsize, recvsize
- rozmiary buforow we i wy
tcp_handle
- dowolne parametry (niekoniecznie deskryptor gniazda), ktore beda przekazywane do:
readit, writeit
- funkcje czytania z i pisania do jakiegos medium.
Funkcje przejscia do nastepnego, zakonczenia rekordu i sprawdzenia czy juz koniec rekordu:
bool_t xdrrec_skiprecord(XDR *xdrs)
bool_t xdrrec_endofrecord(XDR *xdrs, bool_t sendnow)
bool_t xdrrec_eof(XDR *xdrs)
sendnow
oznacza, czy od razu wysylac rekord, czy tylko zapamietywac go w buforze (wyslanie wszystkich zgromadzonych komunikatow nastapi wtedy, gdy bufor sie przepelni lub gdy funkcja xdrrec_endofrecord
bedzie wywolana z parametrem sendnow!=0
).
Filtry XDR
Funkcje te sluza do kodowania i dekodowania danych. Ich dzialanie polega na tym, ze otrzymujac w parametrach wskaznik na strukture XDR i wskaznik na zmienna, w zaleznosci od pola XDR->x_op
dokonuja konwersji. Dla wszystkich operacji na okreslonym typie danych definuje sie jedna uniwersalna funkcje, dzieki czemu latwiej uniknac bledow przy pisaniu filtru. Dla kazdego podstawowego typu istnieje filtr XDR, dla wlasnych trzeba go stworzyc, zazwyczaj korzystajac ze standardowych filtrow.
Naglowek wiadomosci RPC:
struct rpc_msg { u_long rm_xid; enum msg_type rm_direction; union { struct call_body RM_cmb; struct reply_body RM_rmb; } ru; };
rm_xid
- identyfikator wiadomosci klientarm_direction
- typ wiadomosci: CALL lub REPLY
struct call_body { u_long cb_rpcvers; u_long cb_prog; u_long cb_vers; u_long cb_proc; struct opaque_auth cb_cred; struct opaque_auth cb_verf; };
cb_rpcvers
- wersja protokolu RPC; musi byc rowna 2cb_cred, cb_verf
- do identyfikacji klienta
struct reply_body { enum reply_stat rp_stat; union { struct accepted_reply RP_ar; struct rejected_reply RP_dr; } ru; };
struct accepted_reply { struct opaque_auth ar_verf; enum accept_stat ar_stat; union { [...] struct { caddr_t where; xdrproc_t proc; } AR_results; } ru; };
ar_verf
- do identyfikacji klientaar_stat
- oznacza czy procedura byla i czy zadzialalaAR_results
- rezultat dzialania procedury zdalnej, te pola sa ustawiane przez klienta przed odbiorem wynikow; odpowiedni filtr XDR dla accepted_reply
zamiast kodowac/rozkodowac AR_results
wywoluje (ru.AR_results.proc)(xdrs, ru.AR_results.where)
,where
- adres wynikow procedury zdalnejproc
- filtr XDR rozkodowujacy wynik
struct rejected_reply { enum reject_stat rj_stat; union { [...] } RJ_versions; enum auth_stat RJ_why; } ru; };
RJ_why
- dlaczego odrzucono komunikatRJ_versions
- dodatkowe informacje na temat powodu odrzucenia
Dla wiadomosci od klienta poczatek naglowka jest taki sam (nie do konca, patrz opis klientow) dla wszystkich wywolan procedur, wiec jest kodowany tylko raz. Za tym fragmentem sa przesylane: dalsza czesc naglowka (zakodowany nr procedury, dane na temat autentycznosci i identyfikacji klienta) i zakodowane parametry procedury.
W przypadku wiadomosci od serwera rezultat jest juz zawarty w strukturze rpc_msg
.
Mimo, ze struktury te sa bardzo rozbudowane, dzieki odpowiednim filtrom XDR (xdr_union
) wysylane sa tylko pola uzywane a nie wszystkie.
./rpc/*
- implementacja RPC
/usr/include/rpc/*
/usr/info/libc.info
- opis funkcji dotyczacych gniazd