Chamadas Remotas de Procedimentos (RPC) Chamada Remota de Procedimento (RPC) ou Chamada de Função ou Chamada de Subrotina Método de transferência de controle de parte de um processo para outra parte Procedimentos => permite a divisão do programas em vários pedaços Motivação para RPC Desvantagens do Socket: Abordagem orientada a Comunicação A aplicação não recebe atenção exclusiva Chamadas de Procedimentos Convencionais podem representar send e receive
O Conceito de Procedimentos main proc1 proc2 proc3 proc4 proc5 proc6 proc7 proc8 - Um programa convencional consiste de um ou mais procedimentos, geralmente organizados em uma hierarquia de chamadas. - Uma seta de um procedimento n para um procedimento m significa uma chamada de n para m RPC: Programa Distribuído Computador 1 Computador 2 main proc1 proc2 proc3 proc4 proc5 proc6 proc7 proc8 - A divisão ocorre entre o programa principal e o procedimento 4. - Um protocolo de comunicação é necessário para implementar a chamada remota.
RPC: Modelo de Execução Programa Principal Procedimento A na máquina 1 na máquina 2 (cliente) (servidor) chamada remota proc. A Saída Resposta O processo cliente fica bloqueado durante a execução do procedimento remoto! RPC Objetivo: Tornar mais fácil a implementação de Aplicações Distribuídas Esconde o código de chamadas a rede em procedimentos chamados stubs Stubs -> procedimentos que contêm o código de chamadas a rede. Com stubs o RPC protege os programas de aplicação (cliente e servidor) de preocupações com detalhes como sockets. O RPC inclui uma especificação para formato padrão dos dados (visando interoperabilidade), e nos stubs é que acontece a conversão dos dados No RPC da Sun o padrão para a representação dos dados é o XDR (external Data Representation Standard) Os stubs são gerados automaticamente por algum compilador. Exemplo: O RPCGen da Sun
Passos de uma Chamada Remota de Procedimentos Máquina do Cliente Máquina do Servidor Cliente Empacota Parâmetros Stub do Cliente Desempacota Parâmetros Stub do Servidor Servidor Desempacota Resultado Empacota Resultados KERNEL transporte de mensagens via rede KERNEL transporte de mensagens via rede Chamadas Remotas de Procedimentos (RPC) Idéia do modelo é estender o conceito de chamada de procedimento convencional para ambientes distribuídos. a ênfase é em distribuição e não em concorrência! objetivo é simplificar a programação distribuída, tornando-a semelhante à programação convencional! Remote Procedure Call (RPC): subrotina chamada pode ser função ou procedimento Procedimentos => permitem a divisão do programas em vários pedaços que podem executar em máquinas arbitrárias.
RPC - Implementação O código das chamadas a rede é escondido em procedimentos chamados stubs Stubs -> procedimentos que contêm o código de chamadas a rede. Comstubs o RPC protege os programas de aplicação (cliente e servidor) de preocupações com detalhes como sockets. Cabe aos stubs a passagem de parâmetros entre procedimentos. Máquinas diferentes podem usar representações diferentes de dados como inteiros, caracteres, etc. O que fazer com dados complexos, como listas, etc? Um exemplo de RPC: Sun-RPC Sistema originalmente criado para máquinas Sun. oferecido atualmente em diversos sistemas operacionais! A arquitetura definida inclui: uma linguagem para definição das interfaces (cabeçalhos de procedimentos, etc); a ferramenta RPCGEN, que gera os stubs cliente e servidor automaticamente; uma biblioteca RPC, que pode ser usada diretamente na construção de programas que não usem o PRCGEN; o protocolo de comunicação entre os stubs. Pode utilizar TCP ou UDP
Sun-RPC - Tradução de dados Tradução entre formatos de dados: utilização de uma representação padrão, XDR (external Data Representation Standard). formato origem formato padrão formato destino A conversão é especificada para um conjunto pré-definido de tipos de dados. RPC - Mapeamento Dinâmico de Portas (Portmapper) Mapeia serviços a Portas Em geral está na porta 111 Programa RPC (servidor) Registra (programa,porta) RPC port mapper Socket usado atualmente por este programa Socket em porta bem conhecida usada
RPC - Mapeamento Dinâmico de Portas Portmapper daemon (1) registro svc_register Sistema Remoto (2) (3) (4) clnt_create Sistema Local Passos envolvidos em uma chamada RPC rpcgen - Funcionamento procedimentos servidores prog_proc.c stub servidor cc prog_svc.c especificação RPC prog.x rpcgen prog.h biblioteca RPC programa servidor prog_svc cliente rprog.c prog_clnt.c stub cliente cc programa cliente rprog
rpcgen Exemplo de arquivo de especificação: /* date.x especificação de serviços remotos de data e hora */ program DATE_PROG version DATE_VERS long BIN_DATE(void) = 1; string STR_DATE(long) = 2; = 1; = 0x31234567; Passo 1: Construir uma Aplicaçao Convencional main proxima insere remove busca inicializar
Passo 2: Dividir o programa em duas partes Cliente main proxima Chamadas a Procedimentos Remotos inicializar insere remove busca Programa Remoto Banco de Dados Passo 3: Criar uma Especificação Rpcgen /* rbd.x especificação rpc para um programa de banco de dados que oferece os procedimentos INSERE, REMOVE e BUSCA */ struct example /* estrutura não usada, declarada para ilustrar como rpcgen */ int exfield1; /* constrói rotinas XDR para converter estruturas */ char exfield2; ; program RBDPROG /* nome do programa remoto */ version RDBVERS /* declaração da versão */ int INICIALIZAR(void) = 1; /* primeiro procedimento deste programa */ int INSERE(string) = 2; /* segundo procedimento deste programa */ int REMOVE(string) = 3; /* terceiro procedimento deste programa */ int BUSCA(string) = 4; /* quarto procedimento deste programa */ = 1; /* definição da versão do programa */ = 0x30090949; /* número do programa remoto (deve ser único) */
Passo 4: Rodar o Rpcgen rpcgen rbd.x rdb_svc.c rpcgen rdb.h rdb_clnt.c rdb_xdr.c /* rbd.h */ Rpcgen Arquivo.h struct example int exfield1; char exfield2; ; typedef sruct example example; bool_t xdr_example(); #define RBDPROG (u_long) 0x30090949) #define RDBVERS ((u_long) 1) #define INICIALIZAR ((u_long) 1) extern int *inicializar_1(); #define INSERE ((u_long) 2) extern int *insere_1(); #define REMOVE ((u_long) 3) extern int *remove_1(); #define BUSCA ((u_long) 4) extern int *busca_1();
/* rbd_xdr.c */ #include <rpc/rpc.h> #include rbd.h Rpcgen Arquivo de Conversão XDR bool_t xdr_example(xdrs, objp) XDR *xdrs; example *objp; if (!xdr_int(xdrs, &objp->exfield1)) return(false); if (!xdr_char(xdrs, &objp->exfield2) return(false); return(true); Rpcgen Stub do Cliente /* rbd_clnt.c */ #include <rpc/rpc.h> #include rbd.h int * inicializar_1(argp, clnt) void *argp; CLIENT *clnt; static int res; bzero((char *)&res, sizeof(res)); if (clnt_call(clnt, INICIALIZAR, xdr_void, argp, xdr_int, &res, TIMEOUT)!= RPC_SUCCESS) return (NULL); return (&res); int *insere_1(argp, clnt) char **argp; CLIENT *clnt; static int res; bzero((char *)&res, sizeof(res)); if (clnt_call(clnt, INSERE, xdr_wrapstring, argp, xdr_int, &res, TIMEOUT)!= RPC_SUCCESS) return (NULL); return (&res); int * remove_1(argp, clnt) char **argp; CLIENT *clnt; static int res; bzero((char *)&res, sizeof(res)); if (clnt_call(clnt, REMOVE, xdr_wrapstring, argp, xdr_int, &res, TIMEOUT)!= RPC_SUCCESS) return (NULL); return (&res); int *busca_1(argp, clnt) char **argp; CLIENT *clnt; static int res; bzero((char *)&res, sizeof(res)); if (clnt_call(clnt, BUSCA, xdr_wrapstring, argp, xdr_int, &res, TIMEOUT)!= RPC_SUCCESS) return (NULL); return (&res);
/* rbd_svc.c */ #include <rpc/rpc.h> #include rbd.h Rpcgen Stub do Servidor svc_run(); (void)fprintf( SVC_RUn retornado \n ); exit(1); static void rbdprog_1(); main() SVCXPRT *transp; (void)pmap_unset(rbdprog, RBDVERS); transp = svcudp_create(rpc_anysock); if (transp == NULL) (void) fprintf( Não pode criar serviço udp\n ); exit(1); if (!svc_register(transp, RBCPROG, RBDVERS, rbdprog_1, IPPROTO_UDP)) (void) fprintf( Não pode registrar tal prog.\n ); exit(1); transp = svctcp_create(rpc_anysock, 0, 0); if (transp == NULL) (void) fprintf( Não pode criar serviço TCP\n ); exit(1); if (!svc_register(transp, RBCPROG, RBDVERS, rbdprog_1, IPPROTO_TCP)) (void) fprintf( Não pode registrar tal prog.\n ); exit(1); static void rbdprog_1(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; union char *insere_1_arg; char *remove_1_arg; char *busca_1_arg; argument; char *result; bool_t (*xdr_argument) (), (*xdr_result)(); char *(*local)(); switch (rqstp->rq_proc) case NULLPROC: ( void)svc_sendreply(transp, xdr_void,(char *) NULL); return; case INICIALIZAR: xdr_argument = xdr_void; xdr_result = xdr_int; local = (char *(*)())inicializar_1; break; case INSERE: xdr_argument = xdr_wrapstring; xdr_result = xdr_int; local = (char *(*)()) insere_1; break; Rpcgen Continuação Stub do Servidor case REMOVE: xdr_argument = xdr_wrapstring; xdr_result = xdr_int; local = (char *(*)())remove_1; break; case BUSCA: xdr_argument = xdr_wrapstring; xdr_result = xdr_int; local = (char *(*)()) busca_1; break; default: svcerr_noproc(transp); return; bzero((char*)&argument, sizeof(argument)); if (!svc_getargs(transp, xdr_argument, &argument)) svcerr_decode(transp); return; result = (*local)(&argument, rqstp); if (result!= NULL &&!svc_sendreply(transp, xdr_result, result )) svcerr_systemerr(transp); if (!svc_freeargs(transp, xdr_argument, &argument)) (void)fprintf( Problema nos argumentos\n ); exit(1);
Passo 5: Escrever procedimentos de Interface com o Stub Rotinas de Interface do Cliente /* rbd_cif.c - inicializar, insere, remove, busca */ #include <rpc/rpc.h> #include rbd.h extern CLIENT *handle; /* handle para procedimento remoto */ int inicializar() return *inicializar_1(handle); int insere(item) char *item; char **arg; arg = &item; return *insere_1(arg, handle); int remove(item) char *item; char **arg; arg = &item; return *remove_1(arg, handle); int busca(item) char *item; char **arg; arg = &item; return *busca_1(arg, handle); Rotinas de Interface do Servidor /* rbd_sif.c - inicializar_1, insere_1, remove_1, busca_1 */ #include <rpc/rpc.h> #include rbd.h static int retcode; int *inicializar_1() retcode = inicializar(); return &retcode; int *insere_1(i) char **i; retcode = insere(*i); return &retcode; int *remove_1(i) char **i; retcode = remove(*i); return &retcode; int *busca_1(i) char **i; retcode = busca(*i); return &retcode;
Passo 6: Compilar e Linkar o Programa Cliente cc -c rbd_cif.c -----> rbd_cif.o cc -c rbd.c --------> rdb.o cc -o rbd rbd.o rbd_clnt.o rbd_xdr.o rbd_cif.o Programa Cliente /* rbd.c - main, proxima*/ #include <rpc/rpc.h> #include rbd.h #define RMACHINE localhost /* nome da máquina remota */ CLIENT *handle; /* handle para um procedimento remoto */ int main(argc, argv) int argc; char *argv[]; char palavra[maxword+1]; char cmd; int plvlen; /* tamanho da palavra */ /* Seta a conexão para uma chamada remota de procedimento */ handle = clnt_create(rmachine, RBDPROG, RBDVERS, tcp ); if (handle == 0) printf( Não pode conectar programa remoto/n ); exit(1); while (1) plvlen = proxima(&cmd, palavra); if (plvlen < 0) exit(0); switch (cmd) case I : /*inicialize */ inicializar(); printf( BD inicializado \n ); break; case i : /*insere */ insere(palavra); printf( %s Inserida \n, palavra); break; case b : /*busca */ if (busca(palavra)) printf( %s foi encontrada \n, palavra); else printf( %s não existe \n, palavra); break; case r : /*remove */ if (remove(palavra)) printf( %s removida \n, palavra); else printf( %s não encontrada \n, palavra); break; case q : /*quit */ printf( programa encerrado \n ); exit(0); default: /* entrada ilegal */ printf( Comando inválido \n ); break;
Continuação Programa Cliente int proxima(cmd, palavra) char *cmd, *palavra; int i, ch; ch = getc(stdin); while (ch == ) ch = getc(stdin); if (ch == EOF) return -1; *cmd = (char) ch; ch = getc(stdin); while (ch = ) ch = getc(stdin); if (ch == EOF) return -1; if (ch == \n ) return 0; i = 0; while ((ch!= ) && (ch!= \n )) if (++i > MAXWORD) printf( Erro, palavra muito longa \n ); exit(1); *palavra++ = ch; ch = getc(stdin); return i; Passo 7: Compilar e Linkar o Programa Servidor cc -c rbd_sif.c --------> rbd_sif.o cc -c rbd_srp.c ------> rdb_srp.o cc -o rbddaemon rbd_svc.o rbd_xdr.o rbd_sif.o rbd_srp.o
Programa Servidor /* rbd_srp.c - inicializar, insere, remove, busca*/ #include <rpc/rpc.h> #include rbd.h /* Procedimentos remotos do servidor e dados globais */ char bd[bdsize][maxword+1] /* armazena o dicionário de palavras */ int npalavras = 0; /* número de palavras no dicionário */ int inicializar() npalavras = 0; return 1; int insere(palavra) char *palavra; strcpy(bd[npalavras], palavra); npalavras++; return npalavras; int remove(palavra) char *palavra; int i; for (i=0; i<npalavras; i++) if (strcmp(palavra, bd[i]) == 0) npalavras--; strcpy(bd[i], bd[npalavras]); return 1; return 0; int busca(palavra) char *palavra; int i; for (i=0; i<npalavras; i++) if (strcmp(palavra, bd[i]) == 0 ) return 1; return 0; Passo 8: Iniciar o Servidor e Executar o Cliente Iniciar o servidor em background: rbddaemon& Executar o Cliente: rbd
Possíveis Falhas em Sistemas Baseados em RPC Cliente não consegue localizar o servidor Perda das mensagens de solicitação Perda das mensagens de resposta Queda do Servidor Queda do Cliente RPC - Cliente não consegue localizar o servidor Causas: Servidor está fora do Ar Cliente está usando versão antiga do servidor Soluções: Variáveis com código de retorno indicando erro Exceções (em linguagem que dão suporte a exceções)
RPC - Perda das mensagens de solicitação Causa: problemas na rede Soluções: temporização para chegada da resposta ou mensagem de reconhecimento tempo expira -> retransmissão da mensagem RPC - Perda das mensagens de resposta Causas: perda da solicitação perda da resposta servidor Soluções: Fácil de tratar quando a operação é idempotente: reenvio da solicitação Atribuir número sequencial as solicitações
Semântica de Chamadas Remotas Ao contrário do que acontece em chamadas convencionais, uma das máquinas envolvidas pode falhar e a outra permanecer ativa... Sistema pode garantir que a execução da rotina chamada ocorre: exatamente uma vez difícil!! no mínimo uma vez operações devem ser idempotentes! no máximo uma vez Sistema deve retornar indicações no caso de máquina servidora falhar! RPC - Queda do Servidor Requisição Servidor Requisição Servidor Sem resposta Recepção Execução Queda Sem resposta Recepção Queda Soluções: não pode ser resolvida com números sequenciais retransmissão da mensagem -> problema recai na semântica da execução
RPC - Queda do Cliente Processamento órfão consomem tempo de processamento no caso do cliente se recuperar, a resposta deste processamento pode chegar até ele Soluções: Consistem em estratégias para eliminação dos órfãos. Exemplo: Reencarnação: ao se recuperar, o cliente envia uma mensagem em broadcast para todas as máquinas da rede, declarando um novo início. Os processamentos anteriores são eliminados RPC Assíncrona Chamadas de Procedimentos que não bloqueiam ou não recebem respostas são ditas Assíncronas. A chamada é enviada para o servidor e o controle é retornado imediatamente para o cliente (não há bloqueio do cliente)
Necessidade de RPC Assíncrona Quando a resposta não é necessária, o cliente pode enviar a solicitação e não bloquear. Ex.: print Quando a resposta é necessária, o cliente pode se beneficiar em fazer a chamada e não aguardar a resposta RPC Síncrona X Assíncrona Síncrona send send 1 Assíncrona receive execute send send 2 receive 1 execute 1 send 1 receive processa send receive execute send receive 1 receive 2 receive 2 execute 2 send 2 tempo receive cliente servidor cliente servidor