Do spisu tresci tematu 9

9.2.3. RPC - Klient TCP i UDP




Spis tresci


Wiadomosci ogolne

Roznice w protokolach - patrz Wstep. Struktura opisujaca klienta:

typedef struct __client {
        AUTH    *cl_auth; 
        struct clnt_ops { 
                /* wywolanie zdalnej procedury */
                enum clnt_stat (*cl_call)();
                /* przerwanie wolania procedury */
                void   (*cl_abort)();
                /* pobiera kod bledu */
                void   (*cl_geterr)();
                /* zwalnia pamiec rezultatow */
                bool_t (*cl_freeres)();
                /* usuwa strukture klienta */
                void   (*cl_destroy)();
                /* operacja ioctl()  */
                bool_t (*cl_control)();
        } *cl_ops;
        caddr_t   cl_private; - dane prywatne klienta (zalezne od protokolu)
} CLIENT;
cl_auth - dane do identyfikacji klienta
clnt_ops - operacje klienta, wskazniki na funkcje zalezne od protokolu
cl_abort - w przypadku obu klientow (TCP, UDP) zmienna ta wskazuje na pusta funkcje. Prawdopodobnie funkcja przewidziana dla innych, niestandardowych protokolow.
cl_freeres - funkcja powinna wywolac filtr XDR dla rezultatow z operacja XDR_FREE
cl_control - funkcja umozliwiajaca sprawdzenie/ustawienie czasu czekania na odowiedz i pobrania adresu sieciowego serwera


Klient TCP

Klient TCP jest zaimplementowany w pliku: rpc/clnt_tcp.c. Dane prywatne (wazniejsze pola):

struct ct_data {
  int     ct_sock;          
  struct sockaddr_in ct_addr;      
  char    ct_mcall[MCALL_MSG_SIZE];
  XDR     ct_xdrs;
  [...]
};
ct_sock - gniazdo klienta
ct_addr - adres serwera
ct_mcall - zakodowany standardowy fragment naglowka
ct_xdrs - potok komunikatow XDR

Dzialanie:

Tworzeniem klienta zajmuje sie funkcja:

CLIENT* clnttcp_create(struct sockaddr_in *raddr, u_long prog, u_long vers, int *sockp, u_int sendsz, u_int recvsz)

raddr - adres zdalny lub NULL
prog, vers - identyfikuja program na serwerze
sockp - wczesniej utworzone gniazdo lokalne lub -1 jesli procedura ma utworzyc nowe
sendsz, recvsz - wielkosci buforow TCP

  1. Na poczatku tworzy strukture CLIENT i jesli nie podano adresu portu zdalnej maszyny to przy pomocy funkcji portmappera znajduje ten port.
  2. Potem, o ile nie podano gniazda lokalnego, to funkcja tworzy je i probuje przypisac mu adres portu < 1024 (funkcja bindresvport()), czyli zastrzezony dla root'a. Dla zwyklego uzytkownika bindresvport() zwroci kod bledu, jednak rezultat jej nie jest w ogole sprawdzany ani zapamietywany.
  3. Nastepnie jest nawiazywane polaczenie z serwerem (przez connect(); jesli nie udalo sie wczesniej przypisac numeru portu to connect() robi to automatycznie stosujac funkcje autobind()), ktore jest zamykane dopiero przez clnttcp_destroy().
  4. Kodowany jest poczatkowy fragment naglowka przy pomocy XDRMEM (potok ten nie jest zapamietywany i jest usuwany) i na koniec tworzony jest potok XDRREC do komunikacji z serwerem.
Wywolanie procedury zdalnej to funkcja:

clnttcp_call(CLIENT *h, u_long proc, xdrproc_t xdr_args, caddr_t args_ptr, xdrproc_t xdr_results, caddr_t results_ptr, struct timeval timeout);

  1. Funkcja najpierw zmniejsza o jeden pole rm_xid w zakodowanym fragmencie naglowka(!!!), stad kazda wiadomosc ma unikalny (do pewnego stopnia) identyfikator. Operacja:
    u_long *msg_x_id = (u_long *)(ct->ct_mcall);
    --(*msg_x_id);
  2. Potem nastepuje zakodowanie reszty wiadomosci (nr procedury, dane identyfikacji i parametry procedury). Kodowanie przy pomocy filtru XDRREC powoduje automatyczne wyslanie wiadomosci.
  3. Funkcja odbiera wiadomosci od serwera tak dlugo, az otrzyma wiadomosc z takim samym polem x_id (zazwyczaj wystarczy jeden raz).
  4. Po ustawieniu pol w strukturze accepted_reply: AR_results.where i AR_results.proc na adres bufora i funkcje rozkodowujaca rezultat, funkcja sprawdza poprawnosc rezultatow (tzn. czy procedura zostala wykonana i tozsamosc nadawcy) i rozkodowuje rezultaty wprost pod adres wskazany przez uzytkownika przy wywolaniu funkcji.
Pozostale funkcje (clnttcp_geterr, clnttcp_freeres, clnttcp_abort, clnttcp_control, clnttcp_destroy) sa bardzo proste, a ich dzialanie powinno byc oczywiste.


Klient UDP

Klient dla protokolu UDP został zaimplementowany w pliku: rpc/clnt_udp.c.Dane prywatne:

struct cu_data {
  int    cu_sock;
  struct sockaddr_in cu_raddr;
  XDR    cu_outxdrs;
  u_int  cu_sendsz;
  u_int  cu_recvsz;
  char   *cu_outbuf;
  char   *cu_inbuf;
  [...]
};
cu_sock - gniazdo klienta
cu_raddr - adres serwera
cu_outxdrs - potok XDRMEM
cu_sendsz - rozmiar bufora wyjsciowego
cu_recvsz - rozmiar bufora wejsciowego
cu_outbuf - adres bufora wyjsciowego
cu_inbuf - adres bufora wejsciowego

Warto wspomniec, ze powyzsza struktura oraz bufory wejsciowy i wyjsciowy znajduja sie w jednym bloku pamieci o rozmiarze sizeof(struct cu_data)+cu_sendsz+cu_recvsz, ktory jest pobierany jednym wywolaniem funkcji malloc.

Dzialanie:

Tworzenie:

CLIENT* clntudp_bufcreate(struct sockaddr_in *raddr, u_long prog, u_long vers, struct timeval wait, int *sockp, u_int sendsz, u_int recvsz)

Funkcja powyzsza jest wywolywana z jednolinijkowej funkcji clntudp_create(raddr, prog, vers, wait, sockp), ktora ustawia standardowe wartosci sendsz i recvsz. Dzialanie jest podobne jak w przypadku TCP przed wywolaniem funkcji connect().W tym przypadku nie nawiazuje sie polaczenia. Funkcja tworzy potok XDRMEM, ktorym koduje poczatek naglowka i zapamietuje ten potok.

Wywolanie zdalnej procedury:

clntudp_call(CLIENT *h, u_long proc, xdrproc_t xdr_args, caddr_t args_ptr, xdrproc_t xdr_results, caddr_t results_ptr, struct timeval timeout)

  1. Na poczatku funkcja zwieksza o jeden pole rm_xid w zakodowanym fragmencie naglowka (w TCP bylo zmiejszane, ale i tak nie ma to znaczenia).
  2. Potem dopisuje do zakodowanego fragmentu naglowka numer procedury (oczywiscie zakodowany), dane o autentycznosci i parametry wywolania procedury.
  3. Nastepnie korzystajac z funkcji systemowej sendto() wysyla serwerowi wiadomosc, ustawia pola w accepted_reply: AR_results.where i AR_results.proc na adres bufora i funkcje rozkodowujaca rezultat.
  4. Funkca zaczyna nasluchiwac odpowiedzi przez select(). Jesli po ustalonym czasie odpowiedz nie nadejdzie to ponawiane jest wysylanie (jak kilka razy nie otrzyma sie dpowiedzi to zwracany jest blad).
  5. Wczytuje sie komunikat (recvfrom()) i jesli jego identyfikator x_id jest niezgodny z wyslanym przez klienta to dalej nasluchuje sie (porownanie to nie wymaga rozkodowania wiadomosci, gdyz w pamieci jest trzymany zakodowany identyfikator w naglowku komunikatu od klienta do serwera).
  6. Funkcja rozkodowuje cala wiadomosc, sprawdza jej autentycznosc i konczy dzialanie z odpowiednim kodem.

Tak jak w TCP: pozostale funkcje (clntudp_geterr, clntudp_freeres, clntudp_abort, clntudp_control, clntudp_destroy) sa bardzo proste.


Uproszczone wywolywanie procedur RPC

W przypadku, gdy interesuje nas wywolywanie pojedynczych procedur RPC wygodniej jest uzyc funkcji:

int callrpc(char *host, u_long prognum, u_long versnum, u_long procnum, xdrproc_t inproc, char *in, xdrproc_t outproc, char *out),

ktora sama tworzy klienta RPC (tylko dla protokolu UDP) i wywoluje clntudp_call. Na koniec dzialania tej funkcji struktura klienta nie jest usuwana i moze byc ponownie uzyta przy ponownym wywolaniu funkcji. Usuwana jest dopiero przy wywolaniu funkcji z innym serwerem, numerem programu lub wersji. Implementacja: plik rpc/clnt_simple.c


Bibliografia

  1. Biblioteka LIBC, a raczej pliki: ./rpc/* - implementacja RPC
  2. Pliki naglowkowe: /usr/include/rpc/*
  3. M. Gabbasi, B. Dupouy: ,,Przetwarzanie rozproszone w systemie UNIX'' - opis uzytkowy RPC i XDR
  4. RFC 1014 - XDR
  5. RFC 1057 - RPC
  6. Dokumentacja biblioteki LIBC: /usr/info/libc.info - opis funkcji dotyczacych gniazd
  7. R. Stevens: ,,Programowanie zastosowan sieciowych'' - ogolne wiadomosci o RPC


Autor: Przemyslaw Kozankiewicz