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.
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.
luarpc.createServant podem ter a mesma
interface,
então não está correto distinguir qual deles deve ser chamado pela interface.
Cada objeto servant deve ser associado a uma porta diferente.
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.