17 de dezembro de 2009

Comunicação entre processos : Sockets - Servidor

Em junho de 2009, eu postei um texto introduzindo o assunto "Comunicação entre Processos". Naquela ocasião, dei um exemplo em C++ Builder usando a API do Windows para fazer duas aplicações no mesmo computador compartilhar parte da memória para trocar informações usando File Mapping. Agora, vou complementar o assunto mostrando uma forma básica de fazer com que dois programas na mesma rede possam trocar informações. Para isso, vou usar Sockets.

Um Socket é um componente de software disponibilizado pelo Sistema Operacional para permitir que um programa receba corretamente pacotes de dados direcionados a ele. O Socket não faz qualquer inferência sobre o conteúdo do pacote de dados, podendo ser qualquer sequência de bytes que se faça necessária. A ideia é a seguinte: um programa cria um Socket Server e o ativa para poder receber solicitações. Este Socket Server é caracterizado por um endereço (o IP do próprio computador) e um número de porta de modo que outros programas que conheçam esse par endereço/porta podem se comunicar com o programa servidor.

Mas, se o Socket não estipula um formato para os dados trocados, como é que o Servidor e o Client conseguem se entender ? A interpretação dos dados é de responsabilidade do programa Servidor e é ele quem dita as sequências de bytes que devem ser enviadas para a mensagem fazer sentido. Qualquer Client que queira se comunicar com ele deve saber como montar as sequências de bytes corretamente para ter suas solicitações atendidas. Ou seja, o Servidor estabelece seu próprio protocolo e os Clients devem segui-lo para poderem se comunicar.

Na ABC71, nós construimos um serviço que, entre outras coisas, lê os certificados digitais válidos instalados no Servidor e envia a lista à aplicação Client para que o usuário possa selecionar qual dos certificados deve ser usado para assinar documentos digitalmente. Vou usar esse cenário para exemplificar o uso dos Sockets.

O C++ Builder e o Delphi têm um componente chamado TServerSocket (na minha versão, ele fica na guia de componentes Internet). O trecho abaixo foi montado no C++ Builder como resposta ao evento OnExecute de um projeto para construção de um Serviço:
class TWServiceOmegaNFe : public TService
{
__published: // IDE-managed Components
TServerSocket *ssCert;
void __fastcall ServiceExecute(TService *Sender);
/* ... */
};

/* ... */
void __fastcall TWServiceOmegaNFe::ServiceExecute(TService *Sender)
{
ssCert->Port = 1024;
ssCert->Active = true;
try {
while (!Terminated)
ServiceThread->ProcessRequests (true);
}
__finally {
/* finalizando o serviço ... */
ssCert->Active = false;
}
}

O código acima apenas configura uma porta no Socket e o ativa para iniciar o monitoramento de requisições dos Clients. No exemplo, escolhi e fixei o número 1024 mas pode ser qualquer porta livre. O comando ServiceThread->ProcessRequests (true) instrui o serviço a receber e tratar quaisquer mensagens enviadas diretamente para a thread principal - por exemplo, quando o usuário solicita a interrupção do Serviço através do console de Serviços do Windows.

Quando um socket Client envia uma mensagem para este Servidor, o socket server dispara o evento OnClientRead, ocasião em que o Servidor interpreta a mensagem e devolve as informações solicitadas - ou uma mensagem de erro, se for apropriado:
void __fastcall TWServiceOmegaNFe::ssCertClientRead( TObject *Sender, TCustomWinSocket *Socket)
{
int lLen = Socket->ReceiveLength();
char *lBuffer = NULL;
TMemoryStream *lMemoryStream, *lStream;

if (lLen > 0)
{
lBuffer = new char [lLen + 1];
lMemoryStream = new TMemoryStream ();

/* lStream não pode ser destruído, pois ele será destruído pelo socket */
lStream = new TMemoryStream ();

try {
memset (lBuffer, 0, lLen + 1);
Socket->ReceiveBuf ((void *)lBuffer, lLen);

/* Este server só reconhece a mensagem #GET_CERTIFICATE mas outras podeam ser incluidas aqui*/
if (memcmp (lBuffer, "#GET_CERTIFICATE", 16) == 0)
{
/* Monta em lMemoryStream um XML com os certificados */
BuscaCertificadoDigital (lMemoryStream);
lLen = lMemoryStream->Size;
if (lLen > 0)
{
/* Para que um Client saiba quantos bytes devem ser lidos, inclui essa informação no início */
lStream->Write (&lLen, sizeof (lLen));

/* Acrescenta o Stream com os certificados */
lStream->Write (lMemoryStream->Memory, lLen);

/* Reposiciona o Stream para que o Socket envie todo o conteúdo */
lStream->Seek (0, soFromBeginning);

/* Envia o Stream para o Client */
Socket->SendStream (lStream);
}
else /* Erro ! */
Socket->SendText ("#CERTIFICATE_ERROR");
}
}
__finally {
delete lMemoryStream;
delete [] lBuffer;
}
}
}

Veja que o Socket servidor recebe a mensagem num buffer - um texto que em princípio é interpretado como um nome de função, indicando qual é o tipo de informação solicitada pelo Client - e depois prepara o stream de retorno e o envia usando o próprio Socket servidor. Para o Client, entretanto, a resposta virá em momento diferente, assincronamente, como veremos.

Mostro no próximo post como montar uma aplicação Client para se comunicar com esse Servidor e obter a lista de Certificados.

Mais Informações
Criação de Serviços (parte 1 e parte 2)

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.