Para permitir o uso de Ambientes Virtuais
Colaborativos é necessário que se utiliza algum tipo de ferramenta
de comunicação entre os nodos. Um das possibilidades
existente é usar a biblioteca
RemoteMemory.
Esta biblioteca foi originalmente com
o apoio de Rafael S. Garcia ([email protected]). Esta biblioteca
provê uma área de memória que pode ser compatilhada
por várias máquinas.
O principal objetivo da biblioteca “Remote Memory” é permitir que o usuário tenha a sua disposição uma memória de dados que pode ser compartilhada entre programas que rodam em máquinas diferentes. A partir de uma aplicação servidora, rodando em uma máquina remota, o usuário pode criar aplicações clientes que se conectem a esse servidor e pode compartilhar dados entre as aplicações.
O diagrama da figura abaixo ilustra o funcionamento básico da biblioteca.
Figura - Arquitetura da Remote Memory
MemoryServer 1001
O número 1001 após o nome do programa determina a porta de comunicação a ser usada para receber as conexões dos clientes.
O código fonte deste servidor encontra-se neste link. Para o presente exercício não há a necessidade de recompilar o servidor. Para compilar a uma aplicação usando do DEVCPP, você terá de incluir no projeto a bibliteca de suporte a SOCKETS do DEVCPP: libwsock32.a. Esta biblioteca encontra-se no diretório LIB da instalação do DEVCPP.
No fonte onde será contruída a aplicação-cliente deve ser adicionado arquivo com os cabeçalhos das funções da biblioteca. Para fazer isso, basta-se adicionar a linha abaixo no começo do arquivo fonte:
#include "RemoteMemory.h"Depois disto, a primeira etapa da aplicação-cliente deve ser a conexão com servidor da memória remota compartilhada. O trecho de código a seguir exemplifica como fica a função main.
Note que este código conecta
com um servidor que está rodando na mesma máquina que o cliente,
através da porta número 1000.
Lembre-se de ativar o servidor, pelo DOS, com o comando:#include <stdio.h>
#include <string.h>#include "RemoteMemory.h" // Inclui a biblioteca de memória remota compartilhada
int main ( int argc, char** argv )
{
int result;
char serverIP[30] = "127.0.0.1"; // Nro IP do servidor
unsigned int port = 1000; // porta de conexão com o servidorresult = ConnectToRemoteMemory ( serverIP, port ); // conecta com o servidor
if ( !result ) {
printf ( "Unable to connect to Memory Server at %s.\n", serverIP );
printf ( "Pressione uma tecla para encerrar.");
getchar();
return 0;
}
printf ("Conexão bem sucedida !\n");
printf ( "Pressione uma tecla para encerrar");
getchar();
return 1;
}
MemoryServer 1000O valor retornado pela função ConnectToRemoteMemory( serverIP, port ) é 1 quando for possível estabelecer a conexão e 0 quando ocorrer um erro.
Esta função abre dois tipos de conexão com o servidor: uma conexão via TCP/IP e a outra via UDP. O usuário poderá usar funções dos dois tipos de conexão no mesmo programa, sem problemas.
Para gravar dados na memória remota compartilhada de dados, é possível usar uma das duas funções abaixo:
· void WriteOnRemoteMemoryIP (void *message, WORD address, int size)A primeira função grava o dado na memória remota compartilhada através da conexão TCP/IP enquanto que a segunda função grava o dado via UDP.
· void WriteOnRemoteMemoryUDP (void *message, WORD address, int size)
O Significado dos parâmetros nas duas funções é o seguinte:
· message: ponteiro para o dado que será enviado para o servidor e gravado na memória remota compartilhadaATENÇÃO:
· address: endereço da memória remota compartilhada onde esse dado deve ser gravado
· size: o tamanho (em bytes) do dado que será gravado
A título de exemplo, o código apresentado a seguir faz uso destas funções para escrever um vetor de inteiros na memória compartilahda.É importante que o usuário da biblioteca entenda como se constitui a memória remota compartilhada para trabalhar corretamente com ela.
Cada posição da memória remota compartilhada equivale a 1 byte. Logo, para se gravar 3 valores inteiros em sequência não se pode utilizar, por exemplo, os endereços 3, 4 e 5.
Se o usuário fizer isso, não terá os dados corretos quando for feita uma leitura desses valores.
O mais indicado a fazer, nesse caso, é gravar os dados nos endereços 3 * sizeof(int), 4 * sizeof(int) e 5 * sizeof(int). Com isso, o programador garante que terá seus três valores gravados em sequência e os dados esterão corretos.
Para evitar problemas de sincronização,
antes de iniciar a gravação do vetor, a rotina grava uma
flag "avisando" que os dados disponíveis na memória são
antigos.
Ao concluir a gravação do
vetor, a rotina atualiza a flag com um novo valor avisando que o vetor
já foi atualizado. Este valor será testado pelo cliente que
lê a memoria a fim de saber se pode ler o vetor.
/* ******************************************************
void GravaDadosNaMemoriaRemota()
Esta rotina grava um vetor de 15 inteiros na
memória remota.
Antes de iniciar a gravação, a rotina escreve um
número negativo (-111) na posição 10 da memória
para indicar ao clinte-leitor que os dados ainda
não estão disponíveis.
****************************************************** */
void GravaDadosNaMemoriaRemota()
{
int dado, vetor[15], i;
printf ("Gravando Dados na Memória...\n");// Grava A constante FLAG_DADOS_VELHOS na posição 10 usando uma
// conexão TCP/IP, avisando que os dados gravados ainda não
// foram gravados.
// O Cliente Leitor deverá ficar lendo esta posição até que este valor
// seja alterado para FLAG_DADOS_OKdado = FLAG_DADOS_VELHOS;
WriteOnRemoteMemoryIP (&dado, 10*sizeof(int), sizeof(int));// Grava um vetor de 15 números a partir da posição 12 usando uma
// conexão TCP/IP
for (i=0;i<15;i++)
vetor[i] = (i+1)*10;
WriteOnRemoteMemoryIP (&vetor[0], 12*sizeof(int), 15* sizeof(int));// Grava a constante FLAG_DADOS_OK na posição 10 usando uma conexão TCP/IP,
// avisando que a gravação do vetor foi concluída
dado = FLAG_DADOS_OK;
WriteOnRemoteMemoryIP (&dado, 10*sizeof(int), sizeof(int));
}
· void ReadFromRemoteMemoryIP (void *message, WORD address, int size)Novamente, a primeira função lê o dado da memória remota compartilhada através ad conexão TCP/IP enquanto que a segunda lê o dado via TCP/UDP. Os parâmetros são os seguintes:
·void ReadFromRemoteMemoryUDP(void *message, WORD address, int size)
·message: onde o dado será armazenado quando for lido.A seguir apresenta-se uma rotina que lê os dados de um vetor armazenado na memória remota. Note que, de acordo com o exemplo anterior, a rotiana fica em um loop testando uma flag para saber se os dados a serem lidos já estão atualizados.
· address: o endereço da memória remota compartilhada onde esse dado deve ser gravado.
· size: o tamanho (em bytes) do dado que será gravado.
/* ******************************************************Neste link SimpleMemoryClient está disponível um programa exemplo com estas rotinas. Note que ambas estão no mesmo fonte, permitindo que um mesmo executável seja usado como leitor ou escritor. No início da execução do programa o usuário escolhe se o programa é um leitor ou um escritor. Lembre-se de ativar o servidor antes de executar um cliente.
void LeDadosDaMemoriaRemota()
Esta rotina lê os dados de um vetor de inteiros
gravados na meória remota.
Antes de iniciar a leitura a rotina testa se o
cliente-escritor já terminou a gravação dos dados.
Este teste é feito verificando o valor da posição
de memória 10
****************************************************** */
void LeDadosDaMemoriaRemota()
{
int i, dado, vetor[15];// Este laço fica esperando que o cliente-escritor
// grave uma flag avisando que os dados já estão
// disponíveis
printf("Tentando ler");
do
{
ReadFromRemoteMemoryIP (&dado, 10*sizeof(int), sizeof(int));
printf(".");
} while (dado != FLAG_DADOS_OK); // testa se os dados já são "novos"printf("Dados disponíveis !!\n");
printf("\nDados Lidos:\n");// Lê o vetor de 15 números a partir da posição 12 usando uma
// conexão TCP/IP
ReadFromRemoteMemoryIP (&vetor[0], 12*sizeof(int), 15* sizeof(int));for (i=0;i<15;i++)
printf("[%d]= %d\n", i, vetor[i]);}
Este ambiente é criado a partir de uma aplicação monousuário em que o observador pode:
#define ID_CLIENT_ONE 11Estas contantes alem de identificarem os usuários definem os endereços de duas posições de memória usadas para definir se o cliente é o primeiro ou o segundo a se conectar no servidor. O trecho de código que faz isto é apresentado a seguir.
#define ID_CLIENT_TWO 22
Caso não seja zero, o valor lido é comparado com ID_CLIENT_ONE. Se for igual a ID_CLIENT_ONE então este é o segundo cliente. O valor ID_CLIENT_TWO é gravado na posição ID_CLIENT_TWO.
// **********************************************************************A função de conexão é chamada pelo programa principal (arquivo AVC.cpp). Baseado na identificação obtida pela aplicação-cliente, o programa define a posição e a orientação iniciais do observador.
// void ConnectToTheServer(int &ID)
// faz a conexão com o servidor e cria um ID para o usuário
// retirado do arquivo "AVC_CommFuncs.cpp"
// **********************************************************************
void ConnectToTheServer(int &ID)
{...........
..............
...........printf ("Conexão bem sucedida !\n");
ServerOK = 1;// verifica se já existe um cliente ONE
MyID = 0;
ReadFromRemoteMemoryIP (&dado, ID_CLIENT_ONE*sizeof(int), sizeof(int));
if (dado == 0) // Ainda não existe um cliente ONE !!
{
dado = ID_CLIENT_ONE;
WriteOnRemoteMemoryIP (&dado, ID_CLIENT_ONE*sizeof(int), sizeof(int));
MyID = ID_CLIENT_ONE;
printf("Sou o cliente UM!\n");
setWindowTitles("Client ONE !!");
}
else
{
if (dado == ID_CLIENT_ONE) // Já existe um cliente ONE, logo este é o cliente DOIS
{
dado = ID_CLIENT_TWO;
WriteOnRemoteMemoryIP (&dado, ID_CLIENT_TWO * sizeof(int), sizeof(int));
MyID = ID_CLIENT_TWO;
printf("Sou o cliente DOIS!\n");
setWindowTitles("Client TWO !!");
}
}
if (MyID == 0) // houve algum problema na identificação... (erro na comunicação ??)
{
printf("Identificação não definida !!\n");
ServerOK =0;
setWindowTitles("Identificação não definida !!");
}
ID = MyID;}
...........
..............
...........
UserObject->TranslateBy(0,1.5,5);
...........
..............
...........
if (MyID == ID_CLIENT_ONE)
{
PartnerObject->SetRenderFunctionData(Verde);
}
if (MyID == ID_CLIENT_TWO)
{
PartnerObject->SetRenderFunctionData(Rosa);
// Move the user again if he is the second one
// move the user forward
UserObject->TranslateToOnOBJCS(0,0,-5,TargetObject);
// Rotate it
UserRotY = 180;
UserObject->RotateBy(UserRotY, 0,1,0);
}
// endereço onde será guardada a posição do usuário ONE
#define POS_CLIENT_ONE 100
// endereço onde será guardada a posição do usuário TWO
#define POS_CLIENT_TWO 200// endereço onde será guardada a rotação do usuário ONE
#define ROT_CLIENT_ONE 120
// endereço onde será guardada a rotação do usuário TWO
#define ROT_CLIENT_TWO 220
// **********************************************************************
// void GravaDadosNaMemoriaRemota(double Pos[3], double Rot)
// **********************************************************************
void GravaDadosNaMemoriaRemota(double Pos[3], double Rot)
{if (!ServerOK)
return;if (MyID == ID_CLIENT_ONE)
{
// Grava 3 variáveis "double" na memória, representando a posição do usuário
WriteOnRemoteMemoryIP (&Pos[0], POS_CLIENT_ONE * sizeof(double), 3*sizeof(double));
// Grava 1 variável "double" na memória, representando a orientação do usuário
WriteOnRemoteMemoryIP (&Rot, ROT_CLIENT_ONE * sizeof(double), sizeof(double));
}if (MyID == ID_CLIENT_TWO)
{
// Grava 3 variáveis "double" na memória, representando a posição do usuário
WriteOnRemoteMemoryIP (&Pos[0], POS_CLIENT_TWO * sizeof(double), 3*sizeof(double));
// Grava 1 variável "double" na memória, representando a orientação do usuário
WriteOnRemoteMemoryIP (&Rot, ROT_CLIENT_TWO * sizeof(double), sizeof(double));
}}
// **********************************************************************
// void LeDadosDaMemoriaRemota(double Pos[3], double &rotY)
// **********************************************************************
void LeDadosDaMemoriaRemota(double Pos[3], double &rotY)
{if (!ServerOK)
return;if (MyID == ID_CLIENT_ONE)
{
// Le 3 variáveis "double" na memória, representando a posição do usuário
ReadFromRemoteMemoryIP (&Pos[0], POS_CLIENT_TWO*sizeof(double), 3*sizeof(double));
// Le 1 variável "double" na memória, representando a orientação do usuário
ReadFromRemoteMemoryIP (&rotY, ROT_CLIENT_TWO*sizeof(double), sizeof(double));
}if (MyID == ID_CLIENT_TWO)
{
// Le 3 variáveis "double" na memória, representando a posição do usuário
ReadFromRemoteMemoryIP (&Pos[0], POS_CLIENT_ONE*sizeof(double), 3*sizeof(double));
// Le 3 variáveis "double" na memória, representando a posição do usuário
ReadFromRemoteMemoryIP (&rotY, ROT_CLIENT_ONE*sizeof(double), sizeof(double));
}
}
// **********************************************************************
// void UpdateRemoteData()
// Faz a leitura dos dados do parceiro no AVC a partir
// da RemoteMemory
// >>>> Arquivo AVC.cpp <<<<
// **********************************************************************
void UpdateRemoteData()
{
double PartnerPos[3], UserPos[3];
SmVR_CPoint User, Zero (0,0,0);// verifica se há um parceiro
if (IsPartnerOnLine())
{
// se houver, torna seu avatar visível
PartnerObject->SetVisibility(TRUE);
// Obtém os dados de sua posição
LeDadosDaMemoriaRemota(PartnerPos, PartnerRotY);printf("Posicao do Parceiro: %7.2f, %7.2f, %7.2f\n", PartnerPos[0], PartnerPos[1], PartnerPos[2]);
// Atualiza a posição/rotacão do avatar
PartnerObject->SetLocalTransformationMatrixToIdentity();
PartnerObject->TranslateBy(PartnerPos[0], PartnerPos[1], PartnerPos[2]);
PartnerObject->RotateBy(PartnerRotY,0,1,0);
}// Envia sua própria posição/rotacão para a RemoteMemoty
UserObject->GetPointInOCS(Zero, &User, RootObject);
UserPos[0] = User.X;
UserPos[1] = User.Y;
UserPos[2] = User.Z;
GravaDadosNaMemoriaRemota(UserPos, UserRotY);}
Para executar a aplicação-cliente
(SmallVR_AVC.exe), rode
primeiro o servidor de memória (arquivo
RodaMemoryServer).
.