Projeto de sistemas distribuídos usando CORBA, parte III : Classe relógio , Classe Servidor e Classe Servant
Vou iniciar o post pedindo desculpas por ter demorado a concluir essa serie de post sobre sistemas distribuídos com CORBA , as disciplinas no doutorado apertaram aqui no DAS e tive que me afastar do blog por um tempo :-) .
Para quem chegou agora , esse post é precedido de 2 posts anteriores de um trabalho da disciplina de sistemas distribuídos , projeto esse feito no 2º trimestre de 2011, confira abaixo os 2 posts anteriores:
Para finalizar essa serie de artigos vamos comentar o funcionamento das seguintes classes:
- Classe relógio , que controla a evolução do relógio lógico de lamport .
-Classe servidor de mensagem , que conforme o próprio nome diz é responsável por implementar o servidor de mensagens
- Classe mensagemServant que é o código que utiliza o corba para responder aos requisitos advindos do servidor.
1- Classe Relógio
A classe relógio utiliza um mecanismo chamado se semáforo para controlar o acesso simultâneo aos seus métodos , isso por que o relógio pode ser acessado a partir de diversas classes e para evitar inconsistências somente um classe pode manipular os valores do relógio uma de cada vez, a classe em sí é uma classe simples com 3 métodos : um set do relógio para setar valores do relógio , um get para consultar os valores e um método tic para fazer o relógio evoluir a medida que ocorrerem os eventos .
Veja abaixo o código da classe Relógio.java
O relógio é sempre inicializado com timestamp 0 , já na inicialização temos a declaração do semáforo que aceita como argumento o numero 1 , isso significa que ele aceita 1 processo por vez na zona critica , parte do código que se acessada simultaneamente pode trazer incongruências .
O método getTimestamp retorna o valor atual do relógio logico , a palavra syncronized torna esse método atômico , ou seja ele tem que rodar até o final antes que sua thread possa ser interrompida .
Os métodos setTimestamp e ticRelogio são auto explicativos e a unica coisa diferente é o uso do semáforo . Para acionar o semáforo usamos o sem.acquire( ) , isso cria uma trava no código e somente quando o processo liberar essa trava outro processo concorrente vai poder entrar na zona critica , para um processo liberar o semáforo precisa usar o sem.release( ) , sendo sem uma referencia para um objeto do tipo semáforo , vale ainda notar que o uso dos semáforos deve ocorre dentro de uma clausula try/catch .
2- Classe Servidor de mensagem
O servidor de mensagem roda em modo mulithread por isso quase tudo ocorre no método run , nesse método ele instancia o servidor no nosso exemplo estamos fazendo o software da estação 1 ( são 3 estações ao todo ) logo ele cria um servidor com o nome de Mensagemserv1, uma mesma maquina pode ter mais de um servidor rodando ao mesmo tempo. Finalmente a classe tem um método unbindnoServidor que será utilizado quando se quiser retirar as referencias do servidor no servidor de nomes que é utilizado para localizar os servidores do sistema distribuído , isso é necessário por que quando um servidor sair do ar sua referencia também deve ser apagada do servidor de nomes para que ninguém tente acessa-lo gerando um erro.
Veja abaixo o código da classe ServidorMensagem.java
A maior parte do código está comentada o que explica muitos dos comandos utilizados, vou comentar somente algumas partes mais importantes , na linhas 31 , 32 e 33 se prepara uma string especial para setar algumas configurações do servidor como porta e endereço IP onde o servidor vai rodar . Na linha 57 se registra o servidor de mensagem e o tipo de dado que ele espera receber Mensagemserv1 como nome do servidor e object como tipo de dado que ele recebe. Na Linha 58 se usa o comando nc.Rebind para se registar os dados no servidor de nomes .
Ao final da classe e fora do método run temos o método unbindNoServidor para apagar o registro do servidor do servidor de nomes .
3- Classe MensagenServant
Essa classe é o coração do nosso sistema distribuído , sempre que chegar uma requisição para o servidor o corba vai acionar um o servant correspondente para atender ao pedido , é aqui que grande parte da lógica da aplicação deve ser colocada , essa classe é a maior do programa portanto vamos tentar explica-la por partes .
Nessa primeira parte temos as estruturas de dados para armazenar as mensagens que chegam são 3 array lists que servem para guardar as mensagens dos 3 emissores além de um array adicional que é o buffer de delivery , esse será o buffer que deve entregar as mensagens para nossa aplicação na camada de aplicação , é nesse buffer que teremos que ordenar as mensagens por ordem total seguindo a logica do relógio de Lamport. Além disso temos 4 valores booleanos que indicam quando terminam as mensagens que chegam de cada emissor além de uma variável comunicacaoPendente que indica quando todas as mensagens de todos os servidores já tiverem chegado . Os semáforos são para controlar o acesso simultâneo aos buffers e a variável "para"indica quando tudo estiver terminado inclusive a montagem do buffer de delivery já tiver sido entregue para a camada de aplicação.
Nessa parte do código temos o método sendMensagem , na verdade o esqueleto desse servant foi construído depois que usamos o compilador corba juntamente com o o arquivo de definição IDL (Interface Definition Language) lá estava definido que teríamos um método sendMensagem que enviaria mensagem para o servidor , infelizmente o nome não foi bem escolhido mas entenda que toda vez que fazemos um sendMensagem do lado do cliente o servidor recebe e trata essa mensagem usando esse método do servant .
O funcionamento do método é o seguinte , se a mensagem que está chegando for da minha própria estação ( nesse caso estação 1 ) eu simplesmente armazeno a mensagem no buffer da estação 1 já que do ponto de vista da estação 1 eu já atualizei o relógio no cliente localmente , caso contrário se verifica se a mensagem é do tipo x o que finaliza as mensagens enviadas de um determinado servidor e não atualiza o relógio , nesse caso só se armazena a respectiva mensagem no seu buffer , finalmente se a mensagem não for do tipo x e se a origem da mensagem não for eu mesmo o servidor faz o ajuste no relógio usando o algoritmo de Lamport e em seguida armazena a mensagem no buffer correto.
Agora vamos examinar o método de armazenagem nos buffers , o funcionamento é simples e o comportamento está repetido para tratar mensagens dos 3 emissores , por isso vamos explicar o funcionamento de um emissor apenas que vai servir para os outros dois.
O primeiro if testa a origem da mensagem para que possamos separa cada mensagem em seu próprio buffer, depois de testar a origem verificamos se o tipo da mensagem é x o que indicaria mensagem de finalização de envio de um determinado emissor, se um x acabou de chegar o flag indicador de fim de transmissão de um determinada estação é modificado no nosso caso em1Terminou = true , em seguida utilizamos o semáforo para poder acessar os buffers , isso é necessário porque além do servidor existe a camada de aplicação acessando os buffers a medida que as mensagens estão chegando o uso do semaforo evita erros no acesso deixando um processo por vez acessar os buffers , conforme visto anteriormente o uso de semáforos tem que ser dentro de clausulas try/catch o único comando dentro da seção critica é o comando para armazenar a mensagem no buffer correspondente .
O comportamento descrito na parágrafo anterior se repete no método , uma copia para cada estação num total de 3 , ao final do método fazemos um teste para saber se as flags das 3 estações sinalizam o termino de transmissão e caso isso ocorra modificamos a flag de sinalização pendente para false indicando que não falta chegar nenhuma mensagem.
Nessa parte do código temos o método iniciarMontagem() que serve para instanciar a classe interna MontarBufferFinal que é uma thread a mais que serve para montagem do buffer de delivery, esse método só serve para iniciar essa thread e mais nada ...
A Thread responsavel pela montagem do buffer de delivery funciona no método run da classe MontarBufferFinal , ela tem um laço while infinito, em cada passagem dentro do while é verificado se os buffers não estão vazios ou seja se eles tem mensagens a processar.
Se os bufferes tiverem mensagens usamos semáforos para acessar o primeiro elemento do buffer , veja que como isso é uma thread na verdade recebemos as mensagens , armazenamos no buffer , tratamos os buffers e montamos o buffer de delivery tudo concorrentemente por isso precisamos usar os semáforos para não gerarmos nenhuma incongruência no resultado final.
Depois de pegar um elemento de cada buffer chamamos o método montarBufferDelivery passando as 3 mensagens que acabamos de retirar do inicio de cada buffer como argumento, vamos mostrar e explicar o método montarBufferDelivery a seguir , note que montarBufferDelivery é um método e MontarBufferFinal é uma classe interna que é instanciada como uma thread concorrente .
Ao final fazemos um teste if para ver se as mensagens que acabamos de pegar nos buffers são do tipo x, aqui vale adiantar uma coisa , quando passamos as 3 mensagens o método de montagem do bufferDelivery vai comparar os timestamps das mensagens e ordena-las retirando-as do buffer , uma de cada vez , a medida que o tempo passa os buffers vão esvaziar e só vão sobrar as mensagens de tipo X para sinalizar que terminamos de processar tudo , logo testamos ao final de cada ciclo se só sobraram no buffer mensagens do tipo x , se isso ocorrer damos um break no laço infinito while para que a thread chegue ao final e o processo de montagem termine.
Finalmente chegamos ao método de montagem do buffer de Delivery , nesse método temos primeiramente o comando :
O conjunto de if's aninhados garante que caso tivermos eventos de origens diferentes com o mesmo timestamp vamos seguir a regra adotada ( estação com menor numero tem prioridade) , o teste é o seguinte : if (e1.timestamp == minimoTimestamp ) , a logica aqui é que se o evento que vc esta testando tem timestamp igual ao minimo calculado então ele é o evento que deve ser inserido no buffer de delivery .
Usamos o seguinte comando para armazenar essa mensagem no buffer de delivery :
bufferDelivery.add(em1buffer.remove(0));
Note que inserimos a mensagem no buffer de delivery e ao mesmo tempo removemos a mensagem do buffer original , no exemplo a mensagem estaria no buffer do emissor 1 o numero 0 significa que estamos removendo o primeiro elemento do buffer ( da posição 0) . Note no código que usamos 2 semáforos para cada if isso por que além de acessarmos o buffer dos emissores também acessamos o buffer de delivery concorrentemente nesse mesmo comando por isso precisamos de 2 semáforos .
A classe servant ainda conta com métodos para se acessar os buffers e variáveis de controle, temos um método utilitário usado para limpar os buffers ao final do uso do programa ( caso queiramos rodar o aplicativo novamente) .
O código completo da classe servant você consulta a seguir :
Chegamos ao final desse post , no próximo vamos ver como colocamos o programa de exemplo para funcionar a partir do netbeans , algumas classes vão ficar sem explicação não vou detalhar a camada de aplicação uma vez que a ideia aqui é usar o corba , fiz a camada de aplicação usando o matisse X swing do netbeans , vou disponibilizar o codigo em algum repositorio caso alguem queira baixar e estudar o código todo.
Não esqueça de comentar o post.
Para quem chegou agora , esse post é precedido de 2 posts anteriores de um trabalho da disciplina de sistemas distribuídos , projeto esse feito no 2º trimestre de 2011, confira abaixo os 2 posts anteriores:
Para finalizar essa serie de artigos vamos comentar o funcionamento das seguintes classes:
- Classe relógio , que controla a evolução do relógio lógico de lamport .
-Classe servidor de mensagem , que conforme o próprio nome diz é responsável por implementar o servidor de mensagens
- Classe mensagemServant que é o código que utiliza o corba para responder aos requisitos advindos do servidor.
1- Classe Relógio
A classe relógio utiliza um mecanismo chamado se semáforo para controlar o acesso simultâneo aos seus métodos , isso por que o relógio pode ser acessado a partir de diversas classes e para evitar inconsistências somente um classe pode manipular os valores do relógio uma de cada vez, a classe em sí é uma classe simples com 3 métodos : um set do relógio para setar valores do relógio , um get para consultar os valores e um método tic para fazer o relógio evoluir a medida que ocorrerem os eventos .
Veja abaixo o código da classe Relógio.java
O relógio é sempre inicializado com timestamp 0 , já na inicialização temos a declaração do semáforo que aceita como argumento o numero 1 , isso significa que ele aceita 1 processo por vez na zona critica , parte do código que se acessada simultaneamente pode trazer incongruências .
O método getTimestamp retorna o valor atual do relógio logico , a palavra syncronized torna esse método atômico , ou seja ele tem que rodar até o final antes que sua thread possa ser interrompida .
Os métodos setTimestamp e ticRelogio são auto explicativos e a unica coisa diferente é o uso do semáforo . Para acionar o semáforo usamos o sem.acquire( ) , isso cria uma trava no código e somente quando o processo liberar essa trava outro processo concorrente vai poder entrar na zona critica , para um processo liberar o semáforo precisa usar o sem.release( ) , sendo sem uma referencia para um objeto do tipo semáforo , vale ainda notar que o uso dos semáforos deve ocorre dentro de uma clausula try/catch .
2- Classe Servidor de mensagem
O servidor de mensagem roda em modo mulithread por isso quase tudo ocorre no método run , nesse método ele instancia o servidor no nosso exemplo estamos fazendo o software da estação 1 ( são 3 estações ao todo ) logo ele cria um servidor com o nome de Mensagemserv1, uma mesma maquina pode ter mais de um servidor rodando ao mesmo tempo. Finalmente a classe tem um método unbindnoServidor que será utilizado quando se quiser retirar as referencias do servidor no servidor de nomes que é utilizado para localizar os servidores do sistema distribuído , isso é necessário por que quando um servidor sair do ar sua referencia também deve ser apagada do servidor de nomes para que ninguém tente acessa-lo gerando um erro.
Veja abaixo o código da classe ServidorMensagem.java
A maior parte do código está comentada o que explica muitos dos comandos utilizados, vou comentar somente algumas partes mais importantes , na linhas 31 , 32 e 33 se prepara uma string especial para setar algumas configurações do servidor como porta e endereço IP onde o servidor vai rodar . Na linha 57 se registra o servidor de mensagem e o tipo de dado que ele espera receber Mensagemserv1 como nome do servidor e object como tipo de dado que ele recebe. Na Linha 58 se usa o comando nc.Rebind para se registar os dados no servidor de nomes .
Ao final da classe e fora do método run temos o método unbindNoServidor para apagar o registro do servidor do servidor de nomes .
3- Classe MensagenServant
Essa classe é o coração do nosso sistema distribuído , sempre que chegar uma requisição para o servidor o corba vai acionar um o servant correspondente para atender ao pedido , é aqui que grande parte da lógica da aplicação deve ser colocada , essa classe é a maior do programa portanto vamos tentar explica-la por partes .
Nessa primeira parte temos as estruturas de dados para armazenar as mensagens que chegam são 3 array lists que servem para guardar as mensagens dos 3 emissores além de um array adicional que é o buffer de delivery , esse será o buffer que deve entregar as mensagens para nossa aplicação na camada de aplicação , é nesse buffer que teremos que ordenar as mensagens por ordem total seguindo a logica do relógio de Lamport. Além disso temos 4 valores booleanos que indicam quando terminam as mensagens que chegam de cada emissor além de uma variável comunicacaoPendente que indica quando todas as mensagens de todos os servidores já tiverem chegado . Os semáforos são para controlar o acesso simultâneo aos buffers e a variável "para"indica quando tudo estiver terminado inclusive a montagem do buffer de delivery já tiver sido entregue para a camada de aplicação.
Nessa parte do código temos o método sendMensagem , na verdade o esqueleto desse servant foi construído depois que usamos o compilador corba juntamente com o o arquivo de definição IDL (Interface Definition Language) lá estava definido que teríamos um método sendMensagem que enviaria mensagem para o servidor , infelizmente o nome não foi bem escolhido mas entenda que toda vez que fazemos um sendMensagem do lado do cliente o servidor recebe e trata essa mensagem usando esse método do servant .
O funcionamento do método é o seguinte , se a mensagem que está chegando for da minha própria estação ( nesse caso estação 1 ) eu simplesmente armazeno a mensagem no buffer da estação 1 já que do ponto de vista da estação 1 eu já atualizei o relógio no cliente localmente , caso contrário se verifica se a mensagem é do tipo x o que finaliza as mensagens enviadas de um determinado servidor e não atualiza o relógio , nesse caso só se armazena a respectiva mensagem no seu buffer , finalmente se a mensagem não for do tipo x e se a origem da mensagem não for eu mesmo o servidor faz o ajuste no relógio usando o algoritmo de Lamport e em seguida armazena a mensagem no buffer correto.
Agora vamos examinar o método de armazenagem nos buffers , o funcionamento é simples e o comportamento está repetido para tratar mensagens dos 3 emissores , por isso vamos explicar o funcionamento de um emissor apenas que vai servir para os outros dois.
O primeiro if testa a origem da mensagem para que possamos separa cada mensagem em seu próprio buffer, depois de testar a origem verificamos se o tipo da mensagem é x o que indicaria mensagem de finalização de envio de um determinado emissor, se um x acabou de chegar o flag indicador de fim de transmissão de um determinada estação é modificado no nosso caso em1Terminou = true , em seguida utilizamos o semáforo para poder acessar os buffers , isso é necessário porque além do servidor existe a camada de aplicação acessando os buffers a medida que as mensagens estão chegando o uso do semaforo evita erros no acesso deixando um processo por vez acessar os buffers , conforme visto anteriormente o uso de semáforos tem que ser dentro de clausulas try/catch o único comando dentro da seção critica é o comando para armazenar a mensagem no buffer correspondente .
O comportamento descrito na parágrafo anterior se repete no método , uma copia para cada estação num total de 3 , ao final do método fazemos um teste para saber se as flags das 3 estações sinalizam o termino de transmissão e caso isso ocorra modificamos a flag de sinalização pendente para false indicando que não falta chegar nenhuma mensagem.
Nessa parte do código temos o método iniciarMontagem() que serve para instanciar a classe interna MontarBufferFinal que é uma thread a mais que serve para montagem do buffer de delivery, esse método só serve para iniciar essa thread e mais nada ...
A Thread responsavel pela montagem do buffer de delivery funciona no método run da classe MontarBufferFinal , ela tem um laço while infinito, em cada passagem dentro do while é verificado se os buffers não estão vazios ou seja se eles tem mensagens a processar.
Se os bufferes tiverem mensagens usamos semáforos para acessar o primeiro elemento do buffer , veja que como isso é uma thread na verdade recebemos as mensagens , armazenamos no buffer , tratamos os buffers e montamos o buffer de delivery tudo concorrentemente por isso precisamos usar os semáforos para não gerarmos nenhuma incongruência no resultado final.
Depois de pegar um elemento de cada buffer chamamos o método montarBufferDelivery passando as 3 mensagens que acabamos de retirar do inicio de cada buffer como argumento, vamos mostrar e explicar o método montarBufferDelivery a seguir , note que montarBufferDelivery é um método e MontarBufferFinal é uma classe interna que é instanciada como uma thread concorrente .
Ao final fazemos um teste if para ver se as mensagens que acabamos de pegar nos buffers são do tipo x, aqui vale adiantar uma coisa , quando passamos as 3 mensagens o método de montagem do bufferDelivery vai comparar os timestamps das mensagens e ordena-las retirando-as do buffer , uma de cada vez , a medida que o tempo passa os buffers vão esvaziar e só vão sobrar as mensagens de tipo X para sinalizar que terminamos de processar tudo , logo testamos ao final de cada ciclo se só sobraram no buffer mensagens do tipo x , se isso ocorrer damos um break no laço infinito while para que a thread chegue ao final e o processo de montagem termine.
Finalmente chegamos ao método de montagem do buffer de Delivery , nesse método temos primeiramente o comando :
minimoTimestamp = Math.min(e1.timestamp, Math.min(e2.timestamp, e3.timestamp));Nesse comando pegamos o menor timestamp entre as 3 mensagens que pegamos dos buffers , lembrando de temos que ordenar as mensagens dentro de buffer de delivery usando timestamps crescentes ( do menor para o maior) em caso de timestamps de mesmo valor vamos usar a ordem no menor numero de estação ou seja de a estação 1 e 3 tiverem uma mensagem com o mesmo timestamp a mensagem da estação 1 vai ser entregue primeiro , eventos com mesmo timestamp vindo de estações diferentes indicam que foram eventos que ocorreram simultaneamente :-) .
O conjunto de if's aninhados garante que caso tivermos eventos de origens diferentes com o mesmo timestamp vamos seguir a regra adotada ( estação com menor numero tem prioridade) , o teste é o seguinte : if (e1.timestamp == minimoTimestamp ) , a logica aqui é que se o evento que vc esta testando tem timestamp igual ao minimo calculado então ele é o evento que deve ser inserido no buffer de delivery .
Usamos o seguinte comando para armazenar essa mensagem no buffer de delivery :
bufferDelivery.add(em1buffer.remove(0));
Note que inserimos a mensagem no buffer de delivery e ao mesmo tempo removemos a mensagem do buffer original , no exemplo a mensagem estaria no buffer do emissor 1 o numero 0 significa que estamos removendo o primeiro elemento do buffer ( da posição 0) . Note no código que usamos 2 semáforos para cada if isso por que além de acessarmos o buffer dos emissores também acessamos o buffer de delivery concorrentemente nesse mesmo comando por isso precisamos de 2 semáforos .
A classe servant ainda conta com métodos para se acessar os buffers e variáveis de controle, temos um método utilitário usado para limpar os buffers ao final do uso do programa ( caso queiramos rodar o aplicativo novamente) .
O código completo da classe servant você consulta a seguir :
Chegamos ao final desse post , no próximo vamos ver como colocamos o programa de exemplo para funcionar a partir do netbeans , algumas classes vão ficar sem explicação não vou detalhar a camada de aplicação uma vez que a ideia aqui é usar o corba , fiz a camada de aplicação usando o matisse X swing do netbeans , vou disponibilizar o codigo em algum repositorio caso alguem queira baixar e estudar o código todo.
Não esqueça de comentar o post.
Muito bom, só faltou o código. Está em algum repositório? abraço
ResponderExcluirColoquei no 4shared mas o link não e permanente , como uso pouco vira e mexe eles apagam meus arquivos ... segue o link:
ResponderExcluirhttp://www.4shared.com/zip/Fv5llsRl/ProjetoCorba.html