Você está na página 1de 13

Servidor DataSnap

Controlando o Servidor DataSnap

Autor : FABIO ALVES FRANCELINO

Todos já sabemos como criar nossas aplicações 3 camadas com Delphi, mas quando você fez isto ficou se
perguntando o que fazer com o aquele formulário que é necessário criar para se adicionar um TRemoteDataModule
ao projeto?

Hoje venho mostrar algumas soluções para utilizarmos este formulário de forma inteligente e produtiva, com
simples técnicas de RemoteProcedures podemos torná-lo uma ferramenta muito útil e até gerenciar as conexões ao
nosso servidor DataSnap além de alertar nossos usuários sobre possíveis quedas do sistema.

Criando seu servidor DataSnap

Devido a outras edições já ensinarem como criar nossos servidores DataSnap não vou perder tempo com os
detalhes. Crie uma nova aplicação e salve seu projeto e sua unit com os respectivos nomes, DBServer.dpr e
untGUIServer.pas. Adicione ao seu projeto um TRemoteDataModule da guia Multitier e nomeie a CoClass como
RDMServer, salve o arquivo como untRDMServer.pas. Pronto nosso servidor DataSnap já esta com a espinha
dorsal pronta, apartir deste ponto você começa a definir suas Querys e Providers, mas vamos dar mais atenção para
nosso form.

Configurando nosso formulário de console

Configure seu formulário conforme os valores das propriedades da tabela abaixo, lembre-se de desenhar os
componentes também.

TfrmGUIServer Propriedades e componentes

Component Property Name Property Value


TForm Name frmGUIServer
Height 390
Width 690
Border BsSingle
Color clWhite
TLabel Name lblTitle
Color clGreen
TClientDataSet Name cdsConnection
TDataSource Name dtsConnection
DataSet cdsConnection
TdbGrid Name dbgDetail
DataSource dtsConnection
TEdit Name txtMsg
TButton Name cmdMsg
Caption Send Msg
TButton Name cmdPause
Caption Pause
TButton Name cmdResume
Caption Resume
TStatusBar Name stbServer

Adicione 5 painéis ao seu TStatusBar e escreva na sua propriedade text os valores:


Painel 1 = Clients.
Painel 3 = Status.
Painel 4 = Running

Adicione os campos da tabela abaixo no seu TClientDataSet:

TClientDataSet Fields.

Field Name DataType Size


id_cnn wideString 6
login_cnn wideString 20
machine_cnn wideString 20
address_cnn wideString 15
start_cnn wideString 20
system_cnn wideString 100

Atenção lembre-se de chamar o comando CreateDataSet do menu dropdown !!!


Neste ponto seu formulário deverá estar parecido com o da Figura 1

Figura 1. Formulário console formatado e com os componentes desenhados


Codificando nosso formulário de console

Vamos codificar nossos componentes e eventos de acordo com a Listagem 1.

Listagem 1. Código fonte.

Código do Formulário TfrmGUIServer

uses … ShellAPI;

type TfrmGuiServer = class(TForm)


  … //Declaração dos componentes omitida.
  private
   FClientCount: Integer;
   FPaused: Boolean;
   procedure SetClientCount(const pValue: Integer);
   procedure SetPaused(const pValue: Boolean);
  public
   procedure AddDetailLine(const pLogin, pSystem, pMachine, pAddress, pIdApp:    WideString);
   procedure DelDetailLine(const pId: WideString);
   function CheckDuplicate(const pLogin,pSystem: WideString): Boolean;
   function NetSend(const pAddress, pMsg: String): Boolean;
   property ClientCount: Integer read FClientCount write SetClientCount;
   property Paused: Boolean read FPaused write SetPaused;
end;

var frmGuiServer: TfrmGuiServer;

implementation

uses untRDMUsuario;

{$R *.dfm}

//Adiciona a linha na listagem


procedure TfrmGuiServer.AddDetailLine(const pLogin, pSystem, pMachine, pAddress, pIdApp: WideString);
begin
  //Insere no DataSet
  with cdsConnection do begin
   //Executa a rotina
   Insert;
   //Seta os valores
   FieldByName('login_cnn').AsString := pLogin;
   FieldByName('system_cnn').AsString := pSystem;
   FieldByName('machine_cnn').AsString := pMachine;
   FieldByName('address_cnn').AsString := pAddress;
   FieldByName('start_cnn').AsString := TimeToStr(Time);
   FieldByName('id_cnn').AsString := pIdApp;
   //Grava o registro
   Post;
  end;
end;

//Exclui a linha na listagem


procedure TfrmGuiServer.DelDetailLine(const pId: WideString);
begin
  //Tenta localizar o item
  if cdsConnection.Locate('id_cnn',pId,[]) = True then
    //Remove o item
    cdsConnection.Delete;
end;

//Procura o login e o systema na listagem


//Retornos: True = Found | False = Not Found
function TfrmGuiServer.CheckDuplicate(const pLogin,pSystem: WideString): Boolean;
begin
  //Tenta localizar o item o dataset e retorna o valor
  Result :=   cdsConnection.Locate('login_cnn;system_cnn',VarArrayOf([pLogin,pSystem]),[]);
end;

//Tenta enviar uma menssagem via net send


//Retornos: True = Enviada | False = Erro
function TfrmGuiServer.NetSend(const pAddress, pMsg: String): Boolean;
var lResult: Integer;
begin
  //Envia uma mensagem para as máquinas
  lResult := ShellExecute(Handle, nil, PChar('Net'), PChar('Send ' + pAddress +   ' ' + pMsg), nil,
  SW_SHOWMINIMIZED);
  //Verifica o retorno
  if lResult <= 32 then
    //Erro
    Result := False
  else
    //Sucesso
    Result := True;
end;

procedure TfrmGuiServer.SetClientCount(const pValue: Integer);


begin
  //Seta a propriedade
  FClientCount := pValue;
  //Atualiza a tela
  stbServer.Panels[1].Text := IntToStr(pValue);

procedure TfrmGuiServer.SetPaused(const pValue: Boolean);


begin
  //Seta a propriedade
  FPaused := pValue;
  //Verifica o valor
  if pValue = True then begin
    //Atualiza a tela
    stbServer.Panels[3].Text := 'Stopped';
    lblTitle1.Font.Color := clMaroon;
  end else begin
    //Atualiza a tela
    stbServer.Panels[3].Text := 'Running';
    lblTitle1.Font.Color := clGreen;
  end;
end;

procedure TfrmGuiServer.cmdPauseClick(Sender: TObject);


begin
  //Para o servidor
  Paused := True;
end;

procedure TfrmGuiServer.cmdResumeClick(Sender: TObject);


begin
  //Roda o servidor
  Paused := False;
end;

procedure TfrmGuiServer.FormDestroy(Sender: TObject);


begin
  //Salva o arquivo
  cdsConnection.SaveToFile(ExtractFilePath(ParamStr(0)) +   'connection.xml',dfXML);
  //Fecha a tabela
  cdsConnection.Close;
end;

procedure TfrmGuiServer.cmdMsgClick(Sender: TObject);


var i: Integer;
begin
  //Verifica se tem algum registro
  if cdsConnection.RecordCount > 0 then begin
    //Posiciona no primeiro registro
    cdsConnection.First;
    //Roda em todos os registros
    for i := 0 to cdsConnection.RecordCount - 1 do begin
      //Executa a rotina
      if NetSend(cdsConnection.FieldByName('machine_cnn').AsString,txtMsg.Text)       = False then begin
        //Exibe a menssagem de erro
        MessageDlg('Erro ao tentar enviar a mensagem para ' +
        cdsConnection.FieldByName('machine_cnn').AsString,mtError,[mbOk],0);
        //Seta a barra de status
        stbServer.Panels[4].Text := 'Erro ao tentar enviar a mensagem para ' +
        cdsConnection.FieldByName('machine_cnn').AsString;
      end else begin
        //Seta a barra de status
        stbServer.Panels[4].Text := 'mensagem enviada com sucesso.';
      end;
      //Move para o próximo registro
      cdsConnection.Next;
     end;
   end else begin
    //Exibe a menssagem de erro
    MessageDlg('Não há usuários conectados no sistema.',mtError,[mbOk],0);
    //Seta a barra de status
    stbServer.Panels[4].Text := 'Não há usuários conectados no sistema.';
   end;
end;

end.

Nesta listagem de código basicamente programamos as funções de inclusão / exclusão de conexões juntamente com
suas sincronizações na tela, pois devemos refletir estas mudanças no número de Clients conectados ao servidor e no
status do servidor. Também desenvolvemos um meio fácil e rápido de enviar uma mensagem para nosso Client
através do comando NetSend, assim podemos avisar que o sistema vai cair dentro de 5 minutos, ai apertamos o
botão pause para nenhum novo Client se conectar. E por fim no evento OnDestroy do form salvamos nossa lista de
Clients em xml com o ClientDataSet para em uma eventual queda inesperada entrarmos em contato com estes
Clients.

Codificando nosso servidor DataSnap

Vamos codificar nosso servidor que esta representado pela unit untRDMServer, para tanto devemos utilizar o Type
Library editor que o Delphi nos disponibiliza no menu Edit. Siga as instruções da Listagem 2. Para ter acesso aos
tipos definidos nos parâmetros e nos retornos das funções vá em Tools->Environment Options->Type Library-
>Language e mude a opção de IDL para Pascal

Listagem 2. TRemoteDataModule

Remote Procedures

const
  ErrFatal = 'Anote os detalhes do erro e entre em contato com o setor de               desenvolvimento do sistema.' +
#10#13;
  ErrTwiceConnect = 'Você já está conectado com o nosso servidor de aplicação.'                     + #13#10 +
                    'Isto significa que este programa já está aberto na sua                     máquina.' + #13#10;0
  ErrServerPaused = 'O servidor no qual esta hospedada esta aplicação está                     parado.' + #13#10 +
'Provavelmente esta ocorrendo algum                     tipo de atualização do sistema.' + #13#10;l

type TRDMServer = class(TRemoteDataModule, IRDMServer)


  … //Declaração de componentes omitida.
  private
    FIdApp: WideString;
    FErrorMsgUsr: WideString;
    FErrorMsgSys: WideString;
    function IncSequence(const pSeqName: WideString): Integer;
  public
    //Criar estas funções no Type Library Editor, atenção ao parâmetro do tipo       var
    //Não é possível digitar diretamente pois o arquivo *.tlb deve ser       configurado
  function ConnectRequest( const pLogin, pMachine, pAddress, pSystem:   WideString;              var pIdApp:
WideString): WordBool; safecall;
  function DisconnectRequest( const pIdApp: WideString): WordBool; safecall;
  function GetServerError( const pType: WideString): WideString; safecall;
end;

implementation

uses untGUIServer;

{$R *.DFM}

class procedure TRDMServer.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string);


begin
  if Register then begin
    inherited UpdateRegistry(Register, ClassID, ProgID);
    EnableSocketTransport(ClassID);
    EnableWebTransport(ClassID);
  end else begin
    DisableSocketTransport(ClassID);
    DisableWebTransport(ClassID);
    inherited UpdateRegistry(Register, ClassID, ProgID);
  end;
end;

//Recebe as solicitações de conexão e não deixa conectar 2 vezes


//Retornos: True = Aceito | False = Rejeitado
function TRDMServer.ConnectRequest(const pLogin, pMachine, pAddress, pSystem: WideString;
var pIdApp: WideString): WordBool;

begin
  with frmGuiServer do begin
   //Verifica o estado
   if cdsConnection.State = dsInactive then begin
     //Cria o DataSet
     cdsConnection.CreateDataSet;
     //Abre a tabela
     cdsConnection.Open;
     //Seta a propriedade
     cdsConnection.LogChanges := False;
   end;
   //Verifica se o servidor esta parado
   if Paused = False then begin
     //Verifica se ja esta conectado
     if CheckDuplicate(pLogin,pSystem) = False then begin
        //Incrementa a variavel
        ClientCount := ClientCount + 1;
        //Gera um id para a conexão
        FIdApp := BuildIdApp;
        //Inclui as info na lista
        AddDetailLine(pLogin,pSystem,pMachine,pAddress,FIdApp);
        //Seta o id da aplicação de retorno
        pIdApp := FIdApp;
        //Aceito
        Result := True;
      end else begin
        //Seta a mensagem de erro
        FErrorMsgUsr := ErrTwiceConnect;
        //Aceito
        Result := False;
      end;
   end else begin
      //Seta a mensagem de erro
      FErrorMsgUsr := ErrServerPaused;
      //Rejeitado
      Result := False;
   end;
 end;
end;

//Processa a notificação de desconexão do client


//Retornos: True = Desconectado | False = Erro
function TRDMServer.DisconnectRequest(const pIdApp: WideString): WordBool;
begin
  with frmGuiServer do begin
  //Decrementa a variavel
  ClientCount := ClientCount - 1;
  //Verifica se acabou todo mundo
   if ClientCount < 0 then ClientCount := 0;
     //Exclui as info na lista
     DelDetailLine(pIdApp);
   end;
end;

//Retorna o ultimo erro da variavel do servidor


//Parametro: '' = MsgUser | 'Usr' = MsgUser | 'Sys' = MsgSys
function TRDMServer.GetServerError(const pType: WideString): WideString;
begin
  //Verifica o tipo de retorno
  if (pType = '') or (UpperCase(pType) = UpperCase('Usr')) then begin
    //Verifica se tem algum erro
    if Length(FErrorMsgUsr) > 0 then
      //Retorna o valor do erro
      Result := FErrorMsgUsr
    else
      //Retorna a mensagem
      Result := 'MsgUser Field Empty.';
  end else if (UpperCase(pType) = UpperCase('Sys')) then begin
    //Verifica se tem algum erro
    if Length(FErrorMsgSys) > 0 then
      //Retorna o valor do erro
      Result := FErrorMsgSys
    else
      //Retorna a mensagem
      Result := 'MsgSys Field Empty.';
  end else
  //Parametro inválido
  Result := 'Invalid Param Value ' + pType;
end;

//Gera o id da Aplicação
//Retornos: Id Gerado
function TRDMServer.BuildIdApp(): WideString;
begin
  //Escreva aqui seu código de identificação
  Randomize;
  Result := IntToStr(RandomRange(111111,999999));
end;

initialization
 TComponentFactory.Create(ComServer, TRDMServer, Class_RDMServer, ciMultiInstance, tmApartment);

end.

Nesta listagem basicamente codificamos nossos procedimentos remotos e um gerador de Ids, Toda vez que um
Client se conectar com nosso servidor ele deverá passar toda sua identificação para ser registrado em nosso
formulário console, deste modo conseguimos até previnir 2 conexões do mesmo computador ou do mesmo login na
mesma aplicação. Para gerar os Ids eu utilizo uma sequence do PostgreSQL, mas você poderá utilizar qualquer
algoritmo de sua conveniência com ou sem Banco de Dados e até validar se um determinado Client deve ou não se
conectar.

Codificando Nossos Clients

Vamos agora criar um Client de exemplo para fazer a conexão com este servidor, devemos nos preocupar aqui em
chamar os procedimentos remotos ConnectRequest e DisconnectRequest nos pontos certos sempre avaliando o
retorno dessas funções remotas. Para tanto crie uma nova aplicação e salve o formulário como untClient.pas e o
projeto como Client.dpr. Configure e desenhe os componentes conforme a tabela abaixo.

TfrmClient Propriedades e componentes

Property
Component Property Value
Name
TForm Name frmClient
Height 106
Width 294
Border BsSingle
TLabel Name lblId
Caption DBServerClient
TButton Name cmdConnect
Caption Connect
TButton Name cmdDisconnect
Caption Disconnect
TSocketConnection Address 127.0.0.1
  ServerName DBServer.RDMServer

Agora vamos codificar nosso formulário como a Listagem 3.

Listagem 3. Codificando o Cliente

Quando chamar os procedimentos remotos

Uses … Winsock; //Não esqueça de declarar esta unit na clausula uses

type TfrmClient = class(TForm)


  … //Declaração dos componentes omitida.
  private
    FIdApp: WideString;
    function GetLogin(): String;
    function GetMachine(): String;
    function GetIpAddress(): String;
  public
end;

var frmClient: TfrmClient;

implementation

{$R *.dfm}
//Pega o numero do IP na Rede
//Retornos: Ip Address
function TfrmClient.GetIpAddress(): String;
var
  lHostEnt: PHostEnt;
  lHostName: array[0..128] of char;
  lIPAddress: Pchar;
begin
  //Pega o nome do computador
  GetHostName(@lHostName, 128);
  //Pega as informações do sistema
  lHostEnt := GetHostByName(@lHostName);
  //Pega o número do IP
  lIpAddress := iNet_ntoa(PInAddr(lHostEnt^.h_addr_list^)^);
  //Retorna o número do IP
Result := String(lIpAddress);
end;

//Pega o Login do usuário no Windows


//Retornos: Login Name
function TfrmClient.GetLogin(): String;
var lSize: DWord;
 begin
  //Seta o tamanho
  lsize := 255;
  SetLength(Result, lSize);
  //Pega o nome do Usuário
  GetUserName(PChar(Result), lSize);
  //Retorna o Login
  Result := String(PChar(Result));
end;

//Pega o nome da máquina na Rede


//Retornos: Machine Name
function TfrmClient.GetMachine(): String;
var lSize: DWord;
 begin
  //Seta o tamanho
  lSize := MAX_COMPUTERNAME_LENGTH + 1;
  SetLength(Result,lSize);
  //Chama a rotina
  GetComputerName(PChar(Result), lSize);
  //Retorna nome do computador
  Result := String(PChar(Result));
end;

procedure TfrmClient.scServerAfterConnect(Sender: TObject);


var
  lLogin: String;
  lMachine: String;
  lIpAddress: String;
begin
  //Pega o login
  lLogin := GetLogin;
  //Pega o nome da maquina
  lMachine := GetMachine;
  //Pega o endereço IP
  lIpAddress := GetIpAddress;
  //Chama a rotina
  if scServer.AppServer.ConnectRequest(lLogin,lMachine,lIpAddress,'Client   1',FIdApp) = False then begin
    //Mostra o Erro
    MessageDlg(scServer.AppServer.GetServerError('Usr'),mtError,[mbOk],0);
    //Disconecta
    scServer.Connected := False;
  end else
    //Mostra o Id no Label
    lblId.Caption := 'DBServer Client ' + FIdApp;
end;

procedure TfrmClient.scServerBeforeDisconnect(Sender: TObject);


begin
  //Deconecta do servidor
  scServer.AppServer.DisconnectRequest(FIdApp);
end;

procedure TfrmClient.cmdConnectClick(Sender: TObject);


begin
  //Tenta abrir a conexão
  scServer.Open;
end;

procedure TfrmClient.cmdDisconnectClick(Sender: TObject);


begin
  //Tenta fechar a conexão
  scServer.Close;
end;

end.

No client codificamos a chamada de ConnectRequest e DisconnectRequest nos eventos AfterConnect e


BeforeDisconnect do TsocketConnection e no evento OnClick dos botões simplesmente abrimos e fechamos a
conexão. Também mostramos o Id gerado por nosso servidor em nosso TLabel. Tente abrir diretamente 2 vezes o
executável do Client e solicitar a conexão, você receberá uma mensagem de erro prevenindo 2 conexões do mesmo
Client. Também implementamos algumas chamadas na API do windows para retornar a identificação do
computador.

Na Figura 2 podemos ver um exemplo do programa rodando


Figura 2. Servidor DataSnap Rodando com a identificação do client1 e client1 tentando se conectar 2 vezes.

Conclusões

Percebemos que em um ambiente seguro, ou seja, dentro de nossa LAN podemos adicionar recursos valiosos ao
nosso servidor DataSnap, aproveitando toda sua infraestrutura de procedimentos remotos. Também adicionamos
recursos para facilitar a manutenção do servidor podendo parar o serviço sem causar grandes transtornos aos
Clients, outro ponto importante na hora da manutenção é avisarmos aos Clients que necessitamos atualizar o
sistema e fazemos isso através do comando NetSend do windows.

Com este exemplo também podemos saber quais sistemas estão se conectando ao nosso servidor DataSnap, uma
vez que você pode programar vários Clients utilizando o mesmo servidor. Agora é só definir sua conexão e suas
tabelas dentro do seu TRemoteDataModule e acessar seu banco de dados preferido.

Este artigo pode despertar varias idéias para você implementar mais controle e robustez ao seu servidor DataSnap.

Para fazer o download do código-fonte clique aqui

Fabio Alves Francelino (francelinofaf@yahoo.com.br) é:


Analista de Sistemas formado pela Universidade Presbiteriana Mackenzie.
Trabalha com ADO e DataSnap em projetos Cliente/Servidor e Multicamadas acessando MSSQL e PostgreSQL.
Desenvolve Componentes, Engines e Jogos em Delphi.

Contribua com o nosso site enviando artigos, dicas e aplicativos para uploader@clubedelphi.net

Você também pode gostar