Programa PDF
Programa PDF
PUCRS – Brazil
http://www.inf.pucrs.br
Number 015
October, 2001
Contact:
http://www.inf.pucrs.br/~lduarte
http://www.inf.pucrs.br/~fldotti
1 Introdução
Nos últimos anos, a mobilidade de código surgiu como uma nova abordagem para
a construção de aplicações distribuídas. Essa nova abordagem baseia-se na idéia de um
processo poder, de forma dinâmica, suspender sua execução corrente em um
determinado local, mover-se ou ser movido para outro local e lá continuar sua execução.
Nessa abordagem, o programador possui controle sobre o momento em que se faz
necessária a movimentação de seu processo e sobre o local para onde tal processo deve
ir. Apesar disso, o processo de movimentação em si é totalmente transparente ao
programador. Transparência essa fornecida por plataformas de suporte à mobilidade,
tais como as plataformas Voyager da ObjectSpace (www.objectspace.com) e Aglets da
IBM (http://www.trl.ibm.co.jp/aglets/index.html).
A partir da idéia de mobilidade de código, foram criados novos paradigmas de
programação como execução remota, código por demanda e agentes móveis [1]. Tendo
como base estes paradigmas, surgiram linguagens de programação que suportam a
criação de aplicações de código móvel. Tais linguagens apresentam características
distintas e, por isso, vantagens e desvantagens. As características dessas linguagens
foram o alvo de estudo deste trabalho através da coleta de informações sobre as diversas
linguagens existentes, identificação de linguagens que podiam ser agrupadas, devido a
características comuns que possuíam, e implementação de aplicações seguindo as
especificidades de cada um dos grupos encontrados.
Este trabalho apresenta, na seção 2, apresenta os grupos de linguagens
identificados durante o estudo e as linguagens de programação com suporte à
mobilidade estudadas de cada grupo, contendo uma breve descrição de suas
características principais. Na seqüência, a seção 3 contém a descrição da aplicação
utilizada como exemplo e as correspondentes implementações em uma linguagem
representante de cada grupo. A seção 4 apresenta uma avaliação das implementações
realizadas, a seção 5 traz as conclusões do estudo e a seção 6 apresenta as referências
bibliográficas utilizadas.
2 Grupos de Linguagens
3
fundamental da linguagem. Ou seja, considera-se a estrutura mais representativa
manipulada pela linguagem e na qual se baseia seu modo de programação. Tal critério,
foi escolhido por parecer ser o mais importante do ponto de vista de quem pretende criar
aplicações com código móvel, já que ele define o paradigma de programação e, é claro,
a estrutura do agente móvel provido pela linguagem. Dessa forma, foram identificadas
as linguagens baseadas em objetos, baseadas em processos e baseadas em funções.
A seguir são apresentados os grupos criados segundo o critério estabelecido e as
principais características das linguagens que os compõem. Para cada grupo serão
apresentadas as linguagens estudadas daquele grupo.
2.1.1 Java
Desenvolvida pela Sun Microsystems, Java [1], [5], [15], [21], [22], [26], [27] é
uma linguagem altamente portável que possui uma sintaxe parecida com C e C++ e
contém bibliotecas que dão suporte ao desenvolvimento de aplicações para o ambiente
da Internet. A sua capacidade de executar em ambientes heterogêneos (portabilidade) se
deve principalmente à forma como seu código executável é gerado: o compilador Java
transforma os código fonte em um código intermediário, independente de plataforma,
chamado de bytecodes; ao executar-se o programa Java, a Máquina Virtual Java (MVJ)
local traduz (interpreta) o código intermediário para a linguagem da máquina na qual
será executado o programa. Como os bytecodes gerados são entendidos por qualquer
MVJ, independentemente do ambiente em que foram gerados, os programas Java
podem executar em qualquer sistema que possua uma MVJ.
Java provê um mecanismo para busca dinâmica de classes - class loader – caso
um nome de classe constante em um programa não possa ser resolvido localmente ou
caso tal busca seja explicitamente requerida pela aplicação. Além disso, há suporte à
transmissão de código via rede a partir de uma requisição explícita do programador.
Java não suporta a migração de códigos que estejam em execução.
Java possui o conceito de interfaces, as quais são esqueletos de classes, mostrando
os métodos que a classe possui. Ou seja, uma interface define os cabeçalhos dos
métodos que devem ser implementados pela classe que queira utilizar essa interface. No
caso das interfaces, Java permite que uma classe implemente múltiplas interfaces.
Além disso, existe também o mecanismo de coleta automática de lixo (garbage
collection) que provê a “destruição” de instâncias de objetos que não sejam mais
4
referenciados, garantindo que não haverá espaços de memória alocados
indefinidamente.
Java é uma das mais populares linguagens de programação com suporte à
mobilidade de código devido, em especial, a sua integração com a tecnologia da World
Wide Web. Isto se deve em muito à existência das applets. Uma applet é uma classe
Java que contém um aplicação completa, que pode ser transmitida juntamente com uma
página HTML para permitir a execução de algum tipo de apresentação ativa ou
interativa ao usuário e/ou para possibilitar a interação com um servidor remoto [15].
Por sua portabilidade e heterogeneidade, Java tem sido utilizada na construção de
sistemas distribuídos e tem servido de base para a criação de plataformas de suporte à
mobilidade de código, podendo citar-se a Voyager da ObjectSpace
(www.objectspace.com) e a Aglets da IBM (http://www.trl.ibm.co.jp/aglets/index.html).
Essas plataformas fornecem bibliotecas Java que estendem a linguagem com operações
para movimentação de código. A movimentação provida por tais bibliotecas é
implementada de duas formas básicas: na primeira possibilidade, o agente que se move
tem seu código transferido para o local de destino, juntamente com os valores atuais de
seus atributos internos; na segunda idéia, além do código e dos valores dos atributos
internos, são transferidas também outras informações de contexto do agente que possam
permitir que ele retome sua execução a partir da instrução seguinte ao comando de
movimentação. Deve-se citar que a implementação segundo a primeira idéia não
permite a retomada de execução do agente movido diretamente do ponto do código
seguinte ao comando de movimentação. A retomada de execução, nesse caso, é
implementada com a possibilidade de o programador fornecer o nome de um método do
agente que será executado assim que o mesmo chegar a seu destino. Ou seja, defini-se o
que será executado pelo agente ao fim de sua movimentação. Cabe lembrar que,
seguindo a característica de portabilidade de Java, o código é interpretado em cada local
em que é recebido, provendo a possibilidade de movimentação entre máquinas
heterogêneas.
Em geral, as bibliotecas que suportam migração construídas sobre Java utilizam o
conceito de proxy para prover independência de localização. Uma proxy é uma estrutura
usada para referenciar um objeto móvel e permitir a comunicação de outros objetos com
ele, sendo que uma proxy de um objeto móvel é criada logo que ocorre a criação deste.
Esta estrutura é necessária para que, componentes que estivessem comunicando-se com
um objeto antes de sua movimentação, possam continuar a comunicarem-se com ele no
novo local em que se encontra. Qualquer objeto que queira comunicar-se com um objeto
móvel, precisa obter uma proxy deste e utilizá-la para receber e enviar mensagens ao
objeto móvel. Ou seja, todas as comunicações do objeto móvel são intermediadas por
proxies, as quais são locais aos objetos que comunicam-se com ele. Com isto,
comunicações remotas só ocorrem entre objetos móveis e suas proxies e entre proxies,
quando há comunicação entre dois objetos móveis. A proxy utiliza o conceito de
interfaces (descrito anteriormente nesta seção) para prover as comunicações que
intermedia. Uma proxy, na realidade, possui, em sua estrutura, uma interface do objeto
móvel que ela referencia, permitindo que ela possa receber ativações de métodos do
objeto móvel e, caso sejam válidas (sejam relativas a métodos constantes na interface
pública do objeto), encaminhá-las ao objeto em questão.
Não há uma área bem definida para utilização de Java. Na verdade, a maioria das
aplicações distribuídas podem ser construídas utilizando as vantagens de Java.
5
2.1.2 Limbo
2.1.3 M0 Messengers
Desenvolvida na University of Geneva, M0 [1], [11], [20], [21], [22], [27] é uma
linguagem estritamente interpretada, ou seja, não gera um código intermediário. M0 não
foi desenvolvida para a criação de aplicações de código móvel, e sim, para prover uma
camada intermediária de suporte à mobilidade de código para as camadas de mais alto
nível. É uma linguagem que lembra muito PostScript
(http://www.adobe.com/products/postscript/)e usa uma estrutura denominada dictionary
para definir uma memória global. Trocas de dados ocorrem somente através de memória
compartilhada acessada através dos dictionaries.
Unidades de execução são os messengers, os quais podem submeter código para a
criação de outros messengers em um local remoto onde esteja uma platform, que é o
ambiente de execução dos messengers. O código enviado é executado logo que
recebido. Messengers são threads que não possuem qualquer tipo de identificação. Ou
seja, não há como referenciar um messenger. Portanto, caso um messenger não tenha
sido programado para cooperar com outros messengers (através dos dictionaries), o
primeiro permanece invisível aos últimos.
Uma thread remota é criada enviando-se um messenger por um channel, que é um
canal de comunicação entre dois hosts, o que implementa o mecanismo de
movimentação de M0.
Mais informações sobre esta linguagem podem ser obtidas na URL
http://cuiwww.unige.ch/tios/msgr.
6
2.1.4 Obliq
Desenvolvida pela DEC, Obliq [1], [4], [10], [21], [22], [27] é uma linguagem
estritamente interpretada e não tipada que foi criada para suportar aplicações
distribuídas orientadas a objetos. É uma linguagem baseada em protótipos, logo, não
existem classes e os objetos são criados pela utilização de objetos já existentes, que são
os protótipos, através da adaptação destes às necessidades do programador. Dessa
forma, todos os métodos necessários para um objeto já estão contidos no próprio objeto,
já que não há superclasses. Computação em Obliq é transparente via rede; isto é, ela não
depende do local onde é executada nem do local onde se encontram as estruturas sobre
as quais ela é realizada.
Qualquer valor pode ser transmitido entre locais diferentes, denominados
execution engines. Novos objetos criados por uma unidade de execução, que é uma
thread, são armazenados localmente. A movimentação de objetos é fornecida pela
clonagem dos mesmos para o local remoto, levando cópias dos valores de atributos
simples do objeto original. Valores de atributos estruturados (um vetor, por exemplo)
são compartilhados pelo objeto original e pelo clone. Acessos ao objeto no local de
origem são redirecionadas ao local atual do mesmo. Além disso, Obliq também suporta
a serialização de objetos e seu modelo de distribuição de objetos é fortemente baseado
no modelo de Modula-3 [14].
Por ser um trabalho acadêmico, Obliq possui poucas utilizações. Pode-se citar
como exemplo de aplicação usando Obliq, um construtor de aplicações distribuídas
chamado Visual Obliq.
Outras informações sobre Obliq podem ser obtidas na URL
http://research.compaq.com/SRC/personal/luca/Obliq/Obliq.html.
2.1.5 Phantom
7
Algumas das aplicações conhecidas criadas sobre Phantom são sistemas
distribuídos para conferências, jogos multi-jogadores e ferramentas para trabalho
colaborativo.
Informações sobre a linguagem Phantom podem ser obtidas na URL
http://www.apocalypse.org/pub/u/antony/phantom/phantom.htm.
2.1.6 Python
2.1.7 TACOMA
8
chegarem os dados do local de origem, uma nova unidade de execução é criada no local
remoto usando o código recebido e o novo agent, então, acessa os dados da briefcase
para a sua inicialização. A fim de permitir ações de um agent baseadas nas ações
anteriores, os agents de TACOMA carregam consigo seu estado em cada local onde
realizaram alguma tarefa. É usado o Tcl-TCP, uma extensão de Tcl que suporta
comunicação TCP, para mover os agents.
2.1.8 Telescript
9
e não de canais. As idéias são similares na medida em que as tuplas servem, tal qual os
canais, para sincronização e comunicação entre processos.
As representantes deste grupo, dentre as relacionadas, são April, KLAIM,
Nomadic Pict e Pict, apresentadas a seguir.
2.2.1 April
10
2.2.2 KLAIM
2.2.3 Pict
11
transmite o mesmo tipo de valor durante toda a sua existência, logo, deve ser criado um
canal para cada tipo que se queira transmitir.
Em princípio, Pict não apresenta um mecanismo para mobilidade de código, mas
tal característica pode ser representada com os recursos existentes na linguagem: cada
processo possui canais associados a ele, que representam os seus meios de comunicação
com outros processos e também formam o seu ambiente de execução; quando o
conjunto de canais de um processo se modifica, pode-se denotar isto como uma
mudança de ambiente de execução, ou seja, uma mudança de localização. Dessa forma,
uma movimentação de um processo ocorre a partir da transformação de seu conjunto de
canais.
Mais informações sobre Pict na URL http://www.cs.indiana.edu/ftp/pierce/pict.
12
execução de comandos. As expressões são formadas pelo uso de funções que
combinam tipos básicos da linguagem. Funções, nestas linguagens são tratadas como
tipos first class, ou seja, funções podem ser recebidas como parâmetros ou retornadas
por outras funções como qualquer outro tipo básico da linguagem. Funções que recebem
funções como argumento e/ou retornam funções são denominadas funções de alta
ordem (high order functions). Funções também podem ser recursivas e polimórficas.
O desenvolvimento de linguagens funcionais foi influenciado, principal e
fundamentalmente, pelo Cálculo Lambda [40], o qual é tido como a primeira linguagem
funcional. Dessa forma, pode-se ver as linguagens de programação funcionais como
versões aprimoradas do Cálculo Lambda original. O Cálculo Lambda baseia-se na
utilização dos aspectos computacionais das funções (entenda-se aqui o termo função no
seu sentido matemático). Um desses aspectos mais importantes é que funções podem ser
aplicadas a elas mesmas; ou seja, permite atingir-se o efeito de recursão sem,
explicitamente, escrever uma definição recursiva. As funções transmitidas e executadas
em locais remotos oferecem a noção de agentes móveis.
As linguagens que compõem este grupo são Facile, Objective Caml e Tycoon e
são descritas, sucintamente, a seguir.
2.3.1 Facile
13
para especificar interfaces de recursos remotos para acesso a valores e tipos dos
mesmos, e um mecanismo para compilação implícita de um elemento transmitido
quando este chega ao seu destino, o que permite que ele seja transformado para uma
forma executável para o ambiente local onde ele se encontra no momento, provendo
portabilidade para a linguagem.
A representação de um agente em Facile é simplesmente a de uma função. Uma
função é denominada de agente caso um comando de movimentação (send) seja
aplicado sobre ela. Dessa forma, não há uma representação específica para funções que
são agentes e funções que não o são; qualquer função pode ser um agente e, portanto,
movida de um local para outro. A primitiva send recebe um argumento (função) e
dispara o processo de marshalling do mesmo para transmissão ao destino da
movimentação.
Um agente pode ser estruturado contendo várias outras funções, sendo que uma
delas é definida como a função principal para execução do código definido para o
agente em questão.
Para mais informações sobre a linguagem Facile e sua especificação, pode-se ir
em http://www.ecrc.de/research/projects/facile/.
2.3.3 Tycoon
14
3 Exemplo de Implementação
15
7. try
8. {
9. // Inicializa plataforma Voyager
10. Voyager.startup();
16
52. double price = 0;
53. String storeName;
54. IRemoteStore storeInt;
55. IAgent agent;
17
109. {
110. return price;
111. }
129. public void goBuy (String product, IRemoteStore store, int amount)
130. {
131. // Move-se para todas as lojas da lista recebida
132. this.product = product;
133. this.amount = amount;
134. try
135. {
136. Agent.of(this).moveTo(store, "atStore");
137. }
138. catch(Exception exception)
139. {
140. System.err.println(exception);
141. }
142. try
143. {
144. Agent.of(this).moveTo(Agent.of(this).getHome());
145. System.out.println("Agente de compra estah de volta!");
146. }
147. catch(Exception exception)
148. {
149. System.err.println(exception);
150. }
151. }
18
Na classe Customer é feita, primeiramente, a criação da lista de produtos (linhas
13 a 18). Nas linhas 19 a 25 é feita a criação das lojas remotas. As lojas são criadas nos
locais recebidos como argumentos (nomes de hosts ou endereços IP). Como a
plataforma Voyager precisa ser instanciada em cada local onde um agente móvel irá
passar, é fornecida também uma porta de comunicação que será utilizada pela instância
Voyager no host onde a loja for criada. A instrução da linha 22 cria a loja remota no
local especificado e retorna uma referência a ela (proxy). Através dessa referência é
possível ativar todos os métodos associados à classe que implementa uma loja remota
(RemoteStore) que constem em sua interface pública, como acontece na linha 24, onde é
passado o nome da loja remota e a lista de produtos através da chamada de um método
via referência.
O trecho da linha 27 à linha 30 corresponde à criação e envio do agente de
consulta. A variável pos é utilizada para receber um número aleatório que refira-se a um
dos produtos da lista, a fim de que produtos diferentes sejam sorteados a cada execução
(linha 29). O código de implementação para a classe em que se baseia o agente de
consulta é apresentado nas linhas 49 a 120.
O método goQuery, invocado pela classe Customer (linha 30) realiza a ativação
do agente de consulta. Este método recebe como argumentos o nome do produto a ser
procurado e a lista de lojas remotas a percorrer. A linha 64 cria um agente contendo
como métodos os métodos da classe QueryAgent. Ou seja, cria-se um agente baseado na
classe QueryAgent. Sendo um agente, pode-se realizar o comando moveTo provido pela
plataforma Voyager, o qual pode ser visto na linha 71. Este método causa a
movimentação do agente criado para um local especificado, passado como argumento.
O outro argumento deste comando representa uma indicação ao agente sobre qual
método ele deve executar ao chegar ao seu destino. Dessa forma, quando o agente de
consulta chega em uma loja, ele executa o método atStore, conforme informa o segundo
argumento do comando moveTo da linha 71. No método atStore, como pode ser visto
no trecho da linha 88 à linha 107, o agente de consulta compara o menor preço já
encontrado para o produto que ele procura com o preço deste mesmo produto na loja em
que está. Caso o preço da loja atual seja menor do que o menor preço já encontrado
(linha 97), o agente de consulta armazena o preço encontrado (linha 99), guarda o nome
da loja (linha 100) e guarda uma referência à loja (linha 101), a qual será usada
posteriormente pelo agente de compra. Após ter percorrido todas as lojas da lista
recebida como argumento e ter realizado as comparações de preços, o agente de
consulta retorna ao local de origem com os resultados (linha 80). A recepção dos dados
do agente de consulta em Customer é feita nas linhas 31 e 32.
O trecho entre as linhas 35, 36 e 37 da classe Customer refere-se à criação e envio
do agente de compra. A classe base deste agente tem seu código apresentado nas linhas
121 a 161. O método da classe BuyerAgent invocado para ativar o agente é o método
goBuy (linhas 129 a 151), o qual recebe como argumentos o nome do produto a
comprar, uma referência à loja onde deve ser efetuada a compra e a quantidade a ser
comprada. Na linha 136 é criado o agente contendo os métodos da classe BuyerAgent
que deve realizar a compra. Como já visto, o comando moveTo faz com que o agente se
mova para o local determinado e execute o método especificado ao chegar lá. O método
a ser executado é o método homônimo ao executado pelo agente de consulta, ou seja, o
método atStore. Neste método, o agente solicita a compra do produto na quantidade que
lhe foi passada e recebe, como retorno, a quantidade comprada (linha 155). De posse da
quantidade comprada, o agente retorna com os resultados ao local de origem (linha
144). Na linha 38 é recuperado o resultado da compra do agente de compra.
19
Os comandos das linhas 10 e 46 da classe Customer executam, respectivamente, a
inicialização e a finalização da plataforma Voyager.
20
30. then
31. # Compara preco atual com o preco da loja
32. # Se preco atual for 0 (inicial) ou for maior do que
33. # o preco encontrado, atualiza valor do preco atual
34. out ("-> Preco encontrado para ")@screen;
35. out (product)@screen;
36. out (" = ")@screen;
37. out (newPrice)@screen;
38. out ("\n")@screen;
39. if ((price = 0) or (price > newPrice)) then
40. price := newPrice;
41. bestLoc := thisLoc;
42. bestName := storeName
43. endif
44. endif;
45. i := i + 1
46. enddo;
47. out ("-> Retornando resultado\n")@screen;
48. # Retorna ao local de origem a informacao do menor preco
49. # encontrado e onde ele foi encontrado
50. go@retLoc;
51. out ("result", price, bestName, bestLoc)@self
52. end;
68. on
69. nodes
70. Customer :: {screen ~ CustomerScreen, Rem1 ~ RemoteStore1, Rem2 ~
RemoteStore2, Rem3 ~RemoteStore3}
71. class "Klava.NodeConsole" messages
72. declare
73. var stores : ts;
74. var price, i, amount : int;
75. var storeName : str;
76. var again : bool;
77. var storeLoc, thisLoc, store, bestLoc : loc
78. begin
79. # Cria lista de lojas remotas
80. out (1, "Store1", Rem1)@self;
81. out (2, "Store2", Rem2)@self;
82. out (3, "Store3", Rem3)@self;
83. i := 1;
84. newloc(stores);
85. again := true;
21
86. while (again) do
87. if read (i, !storeName, !storeLoc)@self within 1 then
88. out (storeName, storeLoc)@stores;
89. i := i + 1
90. else
91. again := false
92. endif
93. enddo;
22
simulá-los através desta utilização de localidades. Nas linhas 85 a 93 as tuplas criadas
anteriormente são adicionadas ao tuple space da localidade recém criada. As linhas 95 e
96 são utilizadas para obter-se a identificação da localidade atual, que será passada
como parâmetro ao novo processo a ser criado, a fim de permitir o seu retorno após
cumprida sua tarefa.
A linha 97 apresenta o comando que causa a criação de um novo processo. O
código que tal processo irá executar está descrito entre as linhas 1 e 52. Este processo
corresponde ao agente móvel que, assim como o QueryAgent da implementação baseada
em objetos, é o encarregado de percorrer as lojas constantes na lista e pesquisar qual
delas possui o menor preço para um determinado produto. O processo QueryAgent
recebe como parâmetros o nome do produto para qual ele deve pesquisar o menor preço,
a lista de lojas a percorrer e a localidade de retorno dos resultados (que, no caso, é o
local onde o processo Customer está executando).
Entre as linhas 16 e 46 estão os comandos para que o agente móvel obtenha cada
loja da lista, vá para a loja remota, obtenha a lista de produtos, selecione o produto
desejado, e compare o preço encontrado com o menor já obtido. O comando in da linha
18 realiza o contrário do comando out, anteriormente citado: se o comando out adiciona
tuplas a um tuple space, o comando in, por sua vez, retira tuplas deste. Uma tupla só é
retirada de um tuple space através de um comando in caso todos os componentes da
tupla sejam compatíveis com os argumentos do comando. Dessa forma, o comando in
(!storeName, !nextLoc)@storeList só retira uma tupla de storeList se no tuple space
desta localidade houver uma tupla cujo o primeiro valor seja uma string e o segundo
uma localidade. Se não houver uma tupla compatível o comando in, como já explanado,
bloqueia até que uma tupla coerente com a tupla procurada passe a existir no tuple
space consultado. O sinal ! antes do argumento significa que, ao encontrar uma tupla
compatível em storeList, storeName receberá o valor string da tupla encontrada e
nextLoc receberá o valor localidade. Pode-se entender que o sinal ! significa que está-se
passando os argumentos por referência.
O comando go da linha 24 causa a movimentação do agente (processo) para a
próxima loja. Os comandos das linhas 27 e 28 são usados para obter a lista de produtos
da loja. Cabe salientar que as instruções de out e in, respectivamente das linhas 27 e 28,
possuem instruções in e out correspondentes a serem executadas pela loja remota
visitada, nas quais a loja remota obtém a solicitação da sua lista de produtos pelo agente
(linha 27) e devolve o tuple space (productList) onde o agente pode obtê-la (linha 28).
O trecho de 29 a 44 refere-se à obtenção das informações do produto procurado e a
comparação do preço atual com o preço encontrado na loja. O comando read da linha
29 realiza o mesmo que o comando in, como uma diferença: o comando read não retira
a tupla do tuple space, mas apenas obtém os seus valores e os compara com os valores
passados como argumentos do comando. O comando within, usado dentro do comando
de seleção, é utilizado para que a procura por uma tupla compatível com os argumentos
dure somente o tempo especificado em milisegundos, já que o comando in é bloqueante
e, caso não fosse encontrada uma tupla correspondente, a aplicação ficaria paralisada
neste ponto. Com um time-out determinado, a aplicação permanece naquele ponto
somente o tempo desejado pelo programador. Cabe citar que, assim como o comando in,
o comando read também é bloqueante, enquanto que o comando out é não-bloqueante.
Terminada a tarefa de percorrer as lojas da lista, o agente retorna ao local de
origem e cria uma tupla contendo os resultados (linhas 50 e 51). Neste momento, o
processo Customer, o qual estava esperando pelos resultados do agente (linha 99), exibe
os resultados obtidos e envia o agente BuyerAgent para efetuar a compra de um
quantidade determinada do produto pesquisado na loja de menor preço. O código a ser
23
executado por esse processo está entre as linhas 53 e 67. O agente BuyerAgent recebe
como argumentos o nome do produto a ser comprado, a quantidade a ser comprada, a
loja na qual deve ser efetuada a compra e o local de retorno dos resultados. Ele move-se
para o local da loja indicada, requisita a compra e obtém a quantidade comprada,
informação que é retornada ao local de origem assim que ele ali chega.
Deve-se informar que as lojas remotas também são processos (nodes) executando.
Seu código de implementação não é apresentado por parecer ser desnecessário à boa
compreensão da aplicação descrita, da mesma forma como ocorreu com a
implementação baseada em objetos, onde o código das lojas remotas também não foi
apresentado pelo mesmo motivo. Também é importante citar que, como a referência a
uma dada localidade lógica recebe um nome que é registrado no node especial Net, duas
localidades registradas na mesma Net não podem ter o mesmo nome. Este nome é, na
verdade, o nome do processo executando que representa uma localidade lógica. Devido
a isso, diferentemente do que pode ser feito na implementação em Java, teve-se que
implementar um processo diferente para cada loja remota, em vez de criar-se apenas um
processo genérico que fosse instanciado para cada loja. Ou seja, teve-se que criar
processos RemoteStore1, RemoteStore2, ..., RemoteStoreN e não apenas um processo
RemoteStore que executasse em cada local onde havia uma loja. Como resultado disso,
tem-se que não há possibilidade de instanciar processos, uma vez que cada processo
registrado como localidade lógica em uma Net deve ter nome único dentro da mesma.
Processos de mesmo nome podem ser registrados em Nets diferentes, mas processos de
Nets diferentes não se comunicam.
24
ao destino, a localidade pode conectar-se a uma localidade raiz (estática) ou a uma
localidade de nível inferior. Alterações na árvore de hierarquia são transparentes ao
programador.
Jocaml utiliza, assim como KLAIM possui a idéia do nodo Net (vide 2.2.2), um
servidor de nomes centralizado em que identificações são registradas e obtidas. Este
servidor de nomes provê a transparência de localidade. Dessa forma, o programador não
precisa saber onde está, fisicamente, uma localidade para onde ele deseja mover uma
localidade móvel ou onde está uma função que ele quer utilizar; basta a ele saber a
identificação da localidade e/ou função e utilizá-la para pesquisar no servidor de nomes,
que é o encarregado de saber a localização física dos componentes.
As implementações em Jocaml são compostas por uma seqüência de definições e
chamadas de funções e/ou definições de localidades, onde podem ser incluídas
definições de novas funções. Todas as definições, sejam de função ou de localidade,
iniciam pela palavra reservada let. O que diferencia uma definição de função da de uma
localidade é o uso da palavra reservada loc.
O código de implementação da localidade cliente da aplicação usada como
exemplo é apresentado na Figura 3. Esta localidade possui várias funções definidas, as
quais são apresentadas entre as linhas 1 a 21. O uso da palavra reservada def logo após a
palavra let define uma função pertencente à localidade em que são definidas. Funções
remotas são a forma utilizada para troca de informações entre localidades. Como já
citado, em Jocaml há um servidor de nomes. Este servidor de nomes é que permite obter
referências a funções e localidades remotas e é, portanto, responsável pela viabilização
da comunicação entre localizações e da movimentação de agentes (localidades móveis).
Para viabilizar isto tudo, são providas, pelo servidor de nomes, duas operações: uma
operação de registro e uma de obtenção de função ou localidade. A operação Ns.register
realiza o registro de uma função ou localidade através de um nome de referência, como
o que é feito na linha 58 da Figura 3. A operação Ns.lookup possibilita obter-se uma
função ou localidade registrada através de seu nome de referência, como na linha 1.
Seguindo este modelo, obviamente impede-se que duas funções ou localidades possuam
o mesmo nome de referência, apesar disso não gerar erro por parte da linguagem, já que
o último registro de uma mesma função ou localidade é assumido. Cada localidade ou
função, portanto, possui apenas uma instância registrada no name server.
A palavra reservada vartype, que aparece em algumas declarações e obtenções
(lookup) de funções ou localidades, é usada para permitir o polimorfismo das funções.
Esta palavra é substituída em tempo de compilação segundo inferência do compilador
(compilador analisa o código e atribui um tipo ao retorno da operação de obtenção de
registro de acordo com o resultado da análise feita). Quando uma função ou localidade é
registrada, seu tipo é armazenado neste registro. Ao ser realizada a operação de
recuperação deste registro, o tipo armazenado é comparado ao tipo inferido pelo
compilador e, caso não sejam compatíveis, é gerada uma exceção.
25
11. reply (p1, s1,
12. n1, num1) else
13. reply (p2, s2,
14. n2, num2)
(* Funcoes para bloqueio e desbloqueio *)
15.let def new_lock () =
16. let def free! () | lock () = reply
17. and unlock () = free () | reply in
18. free () | reply lock, unlock
19.let lock, unlock = new_lock ()
26
53. go back;
54. print_string ("Menor preco = "^ string_of_int price);
55. flush stdout;
56. print_string (" na loja "^ name ^"\n");
57. flush stdout;
(* Realiza compra *)
68. let qtde = qs num in
69. let total = sub (qtde, 20) in
79.Join.server()
Figura 3. Código para localidade cliente da implementação.
27
implementação apresentada) executam suas operações paralelamente e, para garantir-se
que a execução de duas localidades dependentes, como é a situação presente nesta
aplicação, em que buyer_agent deve executar somente após query_agent ter finalizado,
deve-se prover um mecanismo de bloqueio. Este bloqueio leva em consideração que a
ordem de início de execução é a ordem em que as localidades aparecem no código.
Dessa forma, query_agent será inicializado sempre antes de buyer_agent.
A instrução seguinte à instrução de bloqueio, executa o comando go (linha 30)
que realiza a movimentação de query_agent para a localidade indicada (rem1), onde
está a localidade que corresponde à loja remota 1. Os comandos das linhas 31 a 33 são
usados para armazenar informações sobre a loja remota atual, assumindo que ela tem o
menor preço para o produto procurado até o momento. A linha 34 utiliza a função
remota monitor1 (obtida pelo comando da linha 7) para descobrir o valor do produto
monitor na loja atual e armazená-lo em price. Nas linhas 38 a 40 é realizado o
procedimento descrito de movimentação para a loja remota 2 e obtenção do preço para o
produto monitor. A linha 43 apresenta a utilização da função menor para definir se o
preço obtido na loja atual é menor do que o menor preço encontrado até então. A função
referida retorna um conjunto de valores que representam, respectivamente, o menor
entre os dois preços comparados, a identificação da loja remota em que o preço foi
encontrado, o nome da loja remota e o número de identificação da loja. Da linha 45 a
linha 51 é efetuado o mesmo procedimento para o loja remota 3. Na linha 52 é obtida a
referência da localidade original de query_agent e na linha 53 é realizada a sua
movimentação para a localidade em questão, retornando com os resultados. As linhas 58
e 59 servem para registrar a referência à loja remota que contém o produto pelo menor
preço e o seu número de identificação para posterior uso de buyer_agent. A última
instrução (linha 60) serve para desbloquear buyer_agent, já que os dados de que ele
necessitava para executar já estão disponíveis.
O código para a localidade móvel buyer_agent é apresentado entre as linhas 62 e
77. A primeira instrução faz com que a execução fique bloqueada até query_agent
termine (realize o unlock). Após a liberação, os dados de resultado de query_agent são
recuperados (linhas 65 e 66). A localidade buyer_agent é então movida para a loja
remota que possui o menor preço para o produto monitor (linha 67) e requisita a compra
de 20 unidades (linhas 68 e 69). A quantidade comprada é armazenada em total. Nas
linhas 70 a 71 buyer_agent obtém a sua localidade original e retorna com os resultados.
A última instrução da localidade estática home (linha 79) serve para que esta
localidade execute indefinidamente, mantendo os registros feitos por ela e pelas
localidades internas a ela ativos.
28
4.1 Critérios de Avaliação
• Componente móvel: Deve ser fornecida uma entidade que apresente ou possa
apresentar as características de um componente móvel, de acordo com a
abordagem adotada;
A partir dos critérios estabelecidos na seção anterior, esta seção apresenta uma
avaliação das implementações realizadas em relação às linguagens de programação e às
abordagens utilizadas. Cada critério é avaliado separadamente e, ao final, é apresentada
uma tabela comparativa como resultado da avaliação desenvolvida.
29
4.2.1 Identificação
30
(java.io.Serializable). Apesar disso, não é possível dizer que as instâncias de uma classe
que possui estas características são móveis, uma vez que somente o fato de possuir uma
interface definida e ser serializável não determina que um objeto é móvel.
4.2.4 Heterogeneidade
31
heterogeneidade. O que se deve, em grande parte, à utilização de um código
intermediário (os bytecodes) que permite a execução de um componente em qualquer
local que possua um interpretador Java, o qual traduz os bytecodes para a linguagem da
máquina local. KLAIM, a fim de prover heterogeneidade, utiliza-se de uma biblioteca
que transforma o código KLAIM em código Java. Esta biblioteca, chamada de Klava,
possui a implementação em Java das primitivas de KLAIM e permite a execução de um
componente KLAIM em ambientes heterogêneos. Portanto, KLAIM não fornece
sozinha a capacidade de heterogeneidade. O’Caml também não possui capacidade de
prover componentes independentes de arquitetura mas, ao contrário de KLAIM, não
utiliza qualquer mecanismo para obter esta característica. Componentes O’Caml só
executam em ambiente Unix e, recentemente, foi anunciada uma versão que deverá
executar sobre ambiente Windows NT.
32
4.2.6 Abstração de Dados
Poder-se criar tipos abstratos de dados é um recurso bastante útil e, por vezes,
necessário. Tipos abstratos de dados supõem a possibilidade de terem-se arrays,
estruturas, listas e outras formas de expandir os tipos básicos da linguagem. Um tipo
abstrato de dados facilita o armazenamento de grande quantidade de informações e de
dados de diferentes tipos, além de agrupar informações que individualmente podem não
conter significado útil. Um exemplo é a possibilidade de criar-se, para a aplicação
apresentada no capítulo 3, uma lista de lojas remotas a visitar, em que cada elemento da
lista seria uma estrutura contendo o nome da loja, uma referência ao local onde a loja
está e o preço obtido para o produto procurado nesta loja. Isto torna mais fácil relacionar
os valores obtidos para uma mesma loja, já que eles estão agrupados como um elemento
da lista, e também auxilia a utilização desses valores pela facilidade em percorrer e
acessar dados da lista.
A questão da possibilidade de criarem-se tipos abstratos de dados depende muito
da linguagem, mas também é influenciada pela abordagem seguida. A orientação a
objetos já pressupõe tipos abstratos de dados, uma vez que um objeto pode agrupar
muitos valores, que formam seus atributos. Por isso, Java é muito bem provida de
mecanismos para permitir a criação de tipos abstratos e a utilização de alguns já
presentes no pacote da linguagem.
O Cálculo- Qão prevê a utilização de tipos abstratos de dados, já que possui
apenas processos e canais. Os processos trocam dados através dos canais, que suportam
apenas tipos básicos, como inteiros, caracteres e strings. Seguindo a sua abordagem,
KLAIM não possui tipos abstratos de dados e utiliza apenas tipos básicos. A idéia de
um tipo abstrato de dados, no entanto, pode ser simulada pela utilização de tuplas, onde
cada tupla representaria uma estrutura que poderia conter valores de múltiplos tipos; um
conjunto de tuplas de mesmo formato e contendo um valor que seria usado tal como um
índice, poderia ser visto como um array ou um lista.
Na utilização de funções, os tipos básicos são os únicos permitidos como
parâmetros e retornos de funções. Variáveis também só podem assumir valores dos
tipos básicos. O’Caml possui a idéia de uma tupla (conjunto de valores de múltiplos
tipos) ser retornada por uma função, mas os valores deixam de ser trabalhados como um
conjunto de valores logo após o retorno da função, sendo tratados como valores
independentemente a partir de então. Com isso, O’Caml não proporciona qualquer
forma de criarem-se tipos abstratos de dados. É importante dizer que leva-se em
consideração, nessa análise, a programação funcional sobre O’Caml, visto que, segundo
relatado em 2.3.2, O’Caml suporta programação multiparadigma, permitindo criar um
programa que utiliza funções e que trabalha também com objetos. No caso de
trabalharem-se com objetos, obviamente existem tipos abstratos de dados, mas aqui
está-se discutindo O’Caml segundo a programação funcional apenas.
33
Em Java, o tratamento de exceções pode ser feito pelo uso de classes de exceções
que estendam a classe java.lang.Exception, em conjunto com o comando try-catch.
Todos os comandos colocados dentro do bloco try são testados e, caso algum deles
resulte em uma exceção, o bloco de comandos catch é executado. O’Caml provê a
função failwith, a qual recebe como argumento uma string. Esta função é colocada no
bloco de comandos de uma outra função e, caso ocorra alguma exceção nesta última, a
string passada como argumento é exibida como uma mensagem de erro. Ou seja, em
O’Caml só é possível identificar uma exceção, mas não tratá-la. Para KLAIM, a
biblioteca Klava fornece, como em Java, o comando try-catch e uma classe denominada
KlavaException. Ou seja, na verdade, KLAIM utiliza o próprio tratamento de exceções
de Java, apenas tendo a sua própria classe de exceções. Logo, KLAIM não possui, por si
só, mecanismos para tratamento de exceções.
34
o comportamento dos componentes móveis não podem ser monitorados, tornando difícil
o processo de depuração de aplicações que possuem componentes que se movimentam.
Como forma de resumir a análise feita sobre linguagens e respectivas abordagens,
segue uma tabela (vide Tabela 1) contendo os critérios adotados na avaliação e o
resultado para cada uma das linguagens.
O’Caml
Sim Não Função Não Sim Não Não4 Não
(Jocaml)
5 Conclusão
35
facilita, não só a compreensão do código, como também o próprio desenvolvimento
deste. Poderia ter-se uma outra aplicação em que esses aspectos não fossem tão
enfatizados e fosse possível utilizar-se uma linguagem com características diferentes,
que provisse os fatores mais essenciais.
Alguns do aspectos realçados ou desconsiderados em cada linguagem estão
relacionados com a abordagem seguida, mas poder-se-iam criar formas de prover os
quesitos desejáveis em nível de linguagem de programação sem, no entanto, perder-se a
ligação com os conceitos essenciais do paradigma implementado. Isto pode ser
verificado em KLAIM, onde os aspectos de programação seguem o paradigma adotado,
baseado em processos, mas mesmo assim a linguagem fornece recursos a mais do que o
que seria esperado segundo a sua abordagem, principalmente pelo fato de usar
mecanismos de Java, através do pacote Klava, para preencher algumas lacunas de
aspectos desfavorecidos segundo a programação baseada em processos. Outra forma de
englobar todos os aspectos desejados de uma linguagem de programação com suporte à
mobilidade seria, como ocorre em O’Caml, a possibilidade de programação
multiparadigma, o que poderia trazer a soma dos benefícios apresentados por mais de
um paradigma. Neste trabalho, a discussão acerca de O’Caml resumiu-se à programação
funcional por questões de critério de classificação, mas a implementação realizada com
essa linguagem poderia ter sido facilitada pela combinação da abordagem usada com o
paradigma de orientação a objetos, também fornecido por O’Caml.
Conclui-se, portanto, que, em aspectos gerais, as dificuldades ou facilidades de
trabalhar-se com uma abordagem dependem do nível de conhecimento sobre a
linguagem adotada e sobre o próprio modelo de programação do paradigma. Dessa
forma, ao desenvolver uma aplicação móvel, o programador deve levar em consideração
os seus conhecimentos e aptidões e os aspectos mais desejados para a aplicação que ele
deseja construir.
6 Bibliografia
[1] FUGGETA, A., PICCO, G., VIGNA, G.. Understanding Code Mobility,
Transactions On Software Engineering, IEEE, vol. 24, 1998, pp. 342-361.
Disponível em na Internet em http://swarm.cs.wustl.edu/~picco/listpub.html
[3] PIERCE, B., TURNER, D.. Pict: A Programming Language Based on the Pi-
Calculus, Tech. Report 476, Indiana University, 1997. Disponível na Internet
em http://www.cis.upenn.edu/~bcpierce/
[4] KNABE, F. C.. Language Support for Mobile Agents. Ph.D. thesis, Carnegie
Mellon University, 1995. Disponível na Internet em
http://agents.umbc.edu/papers/knabe.shtml
[5] GOSLING, J., McGILTON, H.. The Java Language Environment - A White
Paper. SunMicrosystems, 1996. Disponível na Internet em
http://java.sun.com/doc/language_environment/
36
[6] DE NICOLA, R., FERRARI, G., PUGLIESE, R.. Klaim: a Kernel Language for
Agents Interaction and Mobility, Transactions on Software Engineering, vol.
24, nº 5, IEEE Computer Society, 1998, pp. 315-330.
[7] OUSTERHOUT, J. K.. Tcl and The Tk Toolkit. Addison-Wesley, Reading, MA,
USA, 1994.
[8] GIACALONE, A., MISHRA, P., PRASAD, S.. Facile: A Symmetric Integration
of Concurrent and Functional Programming. International Journal of Parallel
Programming, vol. 18, nº 2, 1989, pp. 121-160.
[10] CARDELLI, L.. Obliq: A Language with Distributed Scope. DEC Systems
Research Center, 1994.
[11] DI MARZO, G., MUHUGUSA, M., TSCHUDIN, C., et al.. The Messenger
Paradigm and Its Implication on Distributed Systems. In Workshop on
Intelligent Computer Communication (ICC). Disponível na Internet em
http://cuiwww.unige.ch/tios/msgr/msgr.html
[15] KNABE, F. C.. An Overview of Mobile Agent Programming. Proc. of the Fifth
LOMAPS Workshop on Analysis and Verification of Multiple-Agent
Languages, number 1192 in Lecture Notes in Computer Science, Stockholm,
Sweden, 1996.
[16] THOMSEN, B., LETH, L., PRASAD, S., et al. Facile Antigua Release
Programming Guide. Technical Report ECRC 93-20, European Computer-
Industry Research Centre, 1993. Disponível na Internet em
http://www.ecrc.de/research/projects/facile/report/report.html
[18] McCABE, F. G., CLARK, K. L.. April – Agent PRocess Interaction Language.
In: Intelligent Agents, ed. Jennings & Wooldridge, LNCS, vol. 890, Springer-
Verlag, 1995.
[19] ROSSUM, G. V.. Python Language Reference Manual. CWI Report, 1992.
Disponível na Internet em http://www.python.org/~guido/Publications.html
37
[20] TSCHUDIN, C. F.. An Introduction to the M0 Messenger Language. University
of Geneva, Swiss, 1994.
[21] CUGOLA, G., GHEZZI, C., PICCO, G. P., et al. Analyzing Mobile Code
Languages. In Mobile Object Systems: Towards the Programmable Internet,
Lecture Notes on Computer Science, vol. 1222, 1997, pp. 93-111. Disponível
na Internet em http://swarm.cs.wustl.edu/~picco/listpub.html
[22] CUGOLA, G., GHEZZI, C., PICCO, G. P., et al. A Characterization of Mobility
and State Distribution in Mobile Code Languages. In Special Issues in
Object-Oriented Programming: Workshop Reader of the 10th European
Conference on Object-Oriented Programming (ECOOP’96), 1996, pp. 309-
318. Disponível na Internet em http://swarm.cs.wustl.edu/~picco/listpub.html
[26] THORN, T.. Programming Languages for Mobile Code. ACM Computing
Surveys, vol. 29, no. 3, 1997, pp. 213-239.
[29] MILNER, R., PARROW, J., WALKER, D.. A Calculus of Mobile Processes
(Parts I and II). Information and Computation, no. 100, 1992, pp. 1-77.
38
[33] CLARK, K. L., McCABE, F. G.. Distributed and Object Oriented Symbolic
Programming in April. Technical Report, Dept. of Computing, Imperial
College, London, 1994.
[34] DE NICOLA, R., FERRARI, G., PUGLIESE, R.. Programming Access Control:
The KLAIM Experience. In Proceedings of CONCUR’2000, LNCS, 2000.
[35] FOURNET, C., GONTHIER, G., LÉVY, J., et al. A Calculus of Mobile Agents.
In Proceedings of CONCUR’96, 1996.
[36] CONCHON, S., LE FESSANT, F.. JoCaml: Mobile Agents for Objective-Caml.
In Ptroceedings of ASA/MA’99, 1999.
[37] FOURNET, C., GONTHIER, G.. The Reflexive Chemical Abstract Machine and
The Join-Calculus. In Proceedings of the 23rd ACM Symposium on
Principals of Programming Languages, pages 372-385, St. Petesburg Beach,
Florida, 1996.
[38] MILNER, R.. The 3RO\DGLF &DOFXOXV D 7XWRULDO ,Q Proceedings of the 1991
Marktoberndorf Summer School on Logic and Algebra of Specification,
1991.
[40] CURCH, A.. The Calculi of Lambda Conversion. Princeton University Press,
Princeton, N. J., 1941.
[41] DORWARD, S., PIKE, R., PRESOTTO, D. L., et al. The Inferno Operating
System. Bell Labs Technical Journal, vol. 2, no. 1, 1997, pp. 5-18.
[42] MOCKAPETRIS, P.. Domain Name – Concepts and Facilities. RFC 1034.
USC/Information Sciences Institute, 1987.
39