Trabalho 2 - Sistemas Distribuídos

Data de entrega: 24/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:

struct { name = "minhaStruct",
         fields = {{name = "nome",
                    type = "string"},
                   {name = "peso",
                    type = "double"}
                   {name = "idade",
                    type = "int"},
                   }
       }
interface { name = "minhaInt",
            methods = {
               foo = {
                 resulttype = "double",
                 args = {{direction = "in",
                          type = "double"},
                         {direction = "in",
                          type = "string"},
                         {direction = "in",
                          type = "minhaStruct"},
                         {direction = "inout",
                          type = "int"}
                        }

               },
               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. 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, s, st, n)
               return a*2, string.len(s) + st.idade + n
             end,
          boo = 
             function (n)
               return n
             end
        }
myobj2 = { foo = 
             function (a, s, st, n)
               return 0.0, 1
             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, "alo", {nome = "Aaa", idade = 20, peso = 50.0})
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.