Trabalho 2 - Sistemas Distribuídos

Data de entrega: 23/4

Biblioteca RPC

Construa um sistema de apoio a chamadas remotas de métodos utilizando Lua e LuaSocket. Implemente uma biblioteca luarpc contendo métodos luarpc.createServant, luarpc.waitIncoming e luarpc.createProxy.

A idéia é partir de uma especificação do objeto remoto como abaixo:

interface { name = minhaInt,
            methods = {
               foo = {
                 resulttype = "double",
                 args = {{direction = "in",
                          type = "double"},
                         {direction = "in",
                          type = "double"},
                         {direction = "out",
                          type = "string"},
                        }

               },
               boo = {
                 resulttype = "void",
                 args = {{ direction = "inout",
                          type = "double"},
                        }
               }
             }
            }
           
Essa é uma especificação que pode ser lida diretamente pelo programa Lua (se você tiver definido uma função chamada interface - a sintaxe de Lua permite que uma função com um único argumento do tipo tabela seja chamada sem os parênteses, como acima). Um arquivo de interface conterá um texto como o do exemplo acima, sempre contendo apenas uma interface. Ela equivale, em uma sintaxe mais tradicional, à seguinte declaracão:
interface minhaInt {
  double foo (in double a, in string b, out string c);
  void boo (inout double a);
};
Os tipos que podem aparecer nessa especificação de interface são char, string, double e void. Os parâmetros podem ser declarados como in, out ou inout.

O seguinte trecho de programa Lua criaria dois servidores com a interface acima:

myobj1 = { foo = 
             function (a, b, s)
               return a+b, "alo alo"
             end,
          boo = 
             function (n)
               return n
             end
        }
myobj2 = { foo = 
             function (a, b, s)
               return a-b, "tchau"
             end,
          boo = 
             function (n)
               return 1
             end
        }
-- cria servidores:
serv1 = luarpc.createServant (myobj1, arq_interface)
serv2 = luarpc.createServant (myobj2, arq_interface)
-- usa as infos retornadas em serv1 e serv2 para divulgar contato 
-- (IP e porta) dos servidores
...
-- vai para o estado passivo esperar chamadas:
luarpc.waitIncoming()

e, por outro lado, o seguinte trecho de programa Lua deve conseguir acessar esse servidor:

...
local p1 = luarpc.createproxy (IP, porta1, arq_interface)
local p2 = luarpc.createproxy (IP, porta2, arq_interface)
local r, s = p1:foo(3, 5)
local t = p2:boo(10)

Como sugerido nesse exemplo, um parâmetro out deve ser tratado como um resultado a mais da função. Um parâmetro inout é mapeado em um argumento de entrada e um resultado a mais.

Observe que tanto o cliente como o servidor conhecem o arquivo de interface. O código cliente deve tentar fazer a conversão dos argumentos que foram enviados para tipos especificados na interface (e gerar erros nos casos em que isso não é possível: por exemplo, se o programa fornece um string com letras onde se espera um parâmetro double).

A função luarpc.waitIncoming pode ser executada depois de diversas chamadas a luarpc.createServant, como indicado no exemplo, e deve fazer com que o processo servidor entre em um loop onde ele espera pedidos de execução de chamadas a qualquer um dos objetos serventes criados anteriormente, atende esse pedido, e volta a esperar o próximo pedido (ou seja, não há concorrência no servidor!). Provavelmente você terá que usar a chamada select para programá-la.

Protocolo de Comunicação

O protocolo de comunicação entre cliente e servidor deve respeitar a descrição a seguir.

O protocolo é baseado na troca de strings ascii. Cada chamada é realizada pelo nome do método seguido da lista de parâmetros in. Entre o nome do método e o primeiro argumento, assim como depois de cada argumento, deve vir um fim de linha. A resposta deve conter o valor resultante seguido dos valores dos argumentos de saída, cada um em uma linha. Caso ocorra algum erro na execução da chamada, o servidor deve responder com uma string iniciada com "___ERRORPC: ", possivelmente seguida de uma descrição mais específica do erro (por exemplo, "função inexistente").

atenção: Esse protocolo está descrito em linhas bastante gerais. Cabe à turma combinar entre si o protocolo exato, pois na entrega final do trabalho o cliente de cada aluno deve conseguir se comunicar com o servidor dos demais.

Testes

Utilize a seguinte interface para testes (com a notação Lua dada acima):

interface inttestes {
  double foo (in double a, in double b, inout double c);
  void bar (void);
  double boo (in string s);
};

Teste chamadas às três funções para verificar correção e para medir desempenho. Faça as medidas do lado do cliente utilizando loops de chamadas.

Teste a função boo com strings de dois tamanhos, um mto pequeno e outro muito grande.

Imagine que você quer criar uma função que trata uma tabela. Como isso não existe na interface, você serializa a tabela (ver livro) e manda uma string. Tente medir o tempo de serialização de uma tabela contendo um array de 100 doubles. Use boo para testar o envio. Teste uma implementação de boo que não de-serializa o argumento recebido, apenas retorna um double qualquer, e, se tiver tempo, outra que faz a de-serialização.

Faça os testes usando um servidor que mantém um pool de 3 conexões abertas e varie o número de clientes usando 1, 3 e 10 clientes. Utilize sempre o *mesmo* cliente em seus testes, e meça os tempos para os dois servidores, o que mantém o pool e o que sempre fecha a conexão.

Escreva um relatório mostrando os resultados alcançados. Na entrega da parte 1, entregue um relatório parcial. Não entregue um monte de tabelas sem comentários a respeito delas! Se houver resultados inesperados, por favor tente explicá-los, possivelmente com novos testes.