DevMedia - asp.net, Java, Delphi, SQL e web Design, tudo em um só lugar!
Bem vindo a DevMedia!
LOGIN:     SENHA:
 
 
DevWare  
Novidade: DevMedia lança o DevWare - Saiba mais!

Artigo Clube Delphi 102 - Messenger Personlizado

Artigo da Revista Clube Delphi Edição 102.

[fechar]

Você não gostou da qualidade deste conteúdo?

(opcional) Você gostaria de comentar o que não lhe agradou?

Esse artigo faz parte da revista Clube Delphi Edição 102. Clique aqui para ler todos os artigos desta edição

Win32

Suporte técnico on-line via chat

Faça você mesmo um aplicativo de Chat usando Delphi

Neste artigo veremos

·         Como utilizar a suíte de componentes Indy;

·         Como criar um protocolo de comunicação;

·         Como utilizar o protocolo criado em um aplicativo de troca de mensagens instantâneas.

Qual a finalidade?

·         Desenvolvimento de um servidor de mensagens no estilo MSN.

Quais situações utilizam esses recursos?

·         Em sistemas que necessitam trocar informações através de uma rede TCP/IP.

 

Resumo do DevMan

         Manter uma forma de comunicação barata e eficiente com o cliente é essencial. E se esse meio de comunicação puder ser personalizado para sua empresa, melhor ainda. Aumentar a comunicação do cliente com o suporte técnico e ter essa preocupação com o bom atendimento, sem dúvida é o diferencial de qualquer empresa. Hoje o mercado tem usado freqüentemente o Messenger da Microsoft para manter um bom relacionamento com seus clientes, porém muitas empresa temem o uso indiscriminado da ferramenta, além é claro do risco de vírus na rede.

         Neste artigo um serviço de troca de mensagens instantâneas será desenvolvido e personalizado de acordo com as nossas necessidades. Criaremos um pequeno protocolo para troca de mensagens e um servidor para distribuir o fluxo de comandos.

 

Acredito que a uma boa parte de nós, programadores que precisam manter contato com seus clientes, utilizam o MSN para prestar suporte técnico. Porém existem alguns clientes que não permitem o uso de aplicativos de mensagem instantânea com medo de que sua rede seja infectada por vírus ou até mesmo por achar que os funcionários ficarão batendo papo o dia todo. Como resolver esse impasse? É aí que o Delphi vem em nosso socorro. Por que não criar um serviço de chat, estilo MSN, só que personalizado para sua empresa? O cliente com esse chat instalado não veria outra coisa a não ser a disponibilidade do suporte e você poderia ver todos os clientes que estão on-line. Com certeza ao mostrar esse aplicativo a esse cliente ele iria se sentir seguro, pois o serviço de comunicação estará com o logo da sua empresa e apenas o setor de suporte estará disponível. Vamos por a mão na massa e agregar valor aos nossos negócios!

Uma visão geral

Nosso DevChat, nome que daremos a nossa aplicação, será composto de 3 partes: um server e dois clients. O server será responsável por manter os clients cadastrados e seu estado. Também caberá a ele direcionar a comunicação entre os clients. Isso significa que para um client enviar uma mensagem de texto para outro, essa deverá ser enviada ao server e então será encaminhada para o destinatário (Figura 1) e para isso o client deve se conectar ao Server por meio da Internet.

 

Figura 1. Interação entre clients e server

Os outros dois clients são muito parecidos. Um será destinado aos clientes e a ele só será permitido enxergar a equipe de suporte. O outro será utilizado pelo suporte e nele estarão sendo exibidos todos os clientes.

Mas o que esperar do nosso DevChat ? Quero implementar junto com vocês as seguintes funcionalidades:

·         Troca de mensagens, formato texto simples: Quem nunca sentiu dificuldade em ler certos diálogos no MSN onde a cada palavra que a pessoa digita do outro lado forma um gif animado pra você? Nosso chat será uma ferramenta de suporte da sua empresa, por isso, isso será cortado.

·         Notificação de mudança de estado: Será possível identificar quem está on-line ou não.

Acredito que com esses requisitos estamos cobrindo o que há de mais básico em termos de comunicação textual on-line.

Construindo o servidor

Como já mencionado, o server deve manter os clients cadastrados.  Para isso vou utilizar um banco de dados Firebird. Na Figura 2 vemos as tabelas que o compõem. Além disso, também é sua responsabilidade validar quem está tentando se conectar. Para que o exemplo faça sentido, crie um banco de dados com o nome que desejar, aqui criamos com o nome DevChat.fdb, e nele crie as tabelas, chaves primárias e estrangeiras conforme o esquema da Figura 2. Não abordarei a criação do banco nesse artigo por não fazer parte do tema.

 

Nota: Não cobrirei aqui a forma que esses clients serão cadastrados, por isso vou inserir alguns registros no banco de dados de forma manual. Seria importante disponibilizar, por exemplo, uma página onde seus clientes pudessem baixar o client e registrar um usuário e senha.

 

Figura 2. Diagrama do banco de dados DevChat.fdb

A comunicação realizada em um aplicativo de Chat se dá através de uma rede TCP/IP, ou seja, para criarmos nosso servidor ele deve ser capaz de utilizar TCP/IP para enviar e receber mensagens. Como fazer isso com Delphi? Quando você instala o Delphi um dos pacotes de componentes que é adicionado é o Indy. Ele é composto por um conjunto de componentes específicos para trabalhar com redes e oferece os mais variados recursos. Neste artigo vamos utilizar a versão disponibilizada junto com o RAD Studio 2007. Portanto crie um novo projeto no RAD Studio e salve-o como DevChatServer.dproj e deixe-o como na Figura 3.

Figura 3. Exemplo de tela do DevChat-Server

Adicione também um Data Module ao projeto e conecte-se ao banco de dados, como na Figura 4. Os componentes qryEmpresa, qryUsuario, qrySuporte simplesmente acessam suas respectivas tabelas e juntamente com os controles de update (upUsuario e upSuporte) permitem que sejam atualizadas informações.

 

Nota: Para conexão com o banco de dados, estamos usando os componentes na paleta Interbase, tais como IBDataBase, IBQuery, IBTransation e IBUpdateSQL. Porém, nada impede que façamos tais conexões utilizando outros componentes de acesso a dados como o dbExpress, por exemplo.


Figura 4. Data Module para conexão ao banco

Ao formulário principal adicione um componente idTCPServer que está localizado na guia Indy Servers. É este componente que pode tornar nosso aplicativo um servidor. Nele configuramos qual porta o DevChat irá utilizar para escutar os clients que desejam se conectar. Para ativar o servidor vamos colocar no evento OnClick do botão Iniciar o código da Listagem 1.

Listagem 1. Iniciando o servidor

procedure TfrmPrincipal.btnIniciarClick(Sender: TObject);

begin

  if btIniciar.Caption = 'Iniciar' then

  begin

    tcpServer.DefaultPort := strtoInt(editPorta.Text);

    tcpServer.Active := true;

    dtm.qryUsuario.Close;

    dtm.qryUsuario.SQL.Clear;

    dtm.qryUsuario.SQL.Text := 'UPDATE USUARIO SET ONLINE = 0, STATUS = 0';

    dtm.qryUsuario.ExecSQL;

    dtm.qryUsuario.Transaction.CommitRetaining;

    dtm.qryUsuario.Close;

    dtm.qrySuporte.Close;

    dtm.qrySuporte.SQL.Clear;

    dtm.qrySuporte.SQL.Text := 'UPDATE SUPORTE SET ONLINE = 0, STATUS = 0';

    dtm.qrySuporte.ExecSQL;

    dtm.qrySuporte.Transaction.CommitRetaining;

    dtm.qrySuporte.Close;

    btIniciar.Caption := 'Parar';

  end

  else

  begin

    tcpServer.Active := false;

    btIniciar.Caption := 'Iniciar'

  end;

  Total := 0;

end;

Se executarmos o programa e clicarmos sobre o botão Iniciar é provável que o firewall do seu sistema operacional seja acionado. Isso acontece porque estamos abrindo uma porta de conexão. No caso do Windows XP, basta confirmar e tudo estará funcionando. Caso o exista algum firewall no seu sistema operacional que não seja o do próprio Windows, talvez seja necessário dar permissão para o sistema que acabamos de desenvolver.

Na Listagem 1 estamos passando, através da propriedade DefaultPort, para o componente tcpServer qual porta será utilizada para manter uma comunicação e então o ativamos. Após isso, ajustamos todos os usuários a terem seu status como off-line apenas fazendo um Update nas tabelas Usuario e Suporte. Isso é necessário, já que em teoria nenhum usuário ou suporte técnico poderia estar on-line com o servidor off-line.

Como dito anteriormente, o servidor ficará escutado as requisições das outras duas aplicações e fará o redirecionamento das mensagens de um para outro. Por isso, precisaremos prever que tipos de mensagem e como elas serão interpretadas no lado servidor.

O que precisamos fazer agora é programar o servidor para que ele seja capaz de interpretar o que recebe e conseqüentemente distribuir as mensagens e comandos recebidos. Usaremos o evento OnExecute do componente TIdTCPServer. Clique duas vezes sobre esse conforme visto na Listagem 2.

Nessa Listagem 2, recebemos o valor passado pelo client através do parâmetro do evento. Em seguida fazemos um copy na variável Texto para que possamos saber qual comando ou mensagem foi passado. Caso a mensagem seja listaSuporte chamamos o método GetUsuariosSuporte. Você precisará criar essa função conforme a Listagem 3. Veja que a tarefa é simples, apenas conectamos ao banco de dados, executamos uma instrução SQL para retornar todos os membros do suporte técnico e carregamos em um TStringList que é passado como retorno.

Listagem 2. Processando o comando de lista de suporte

procedure TServerF.tcpServerExecute(AContext: TIdContext);

var

  comando: string;

  Texto: string;

  ListaSuporte: TStringList;

begin

  Texto:= AContext.Connection.IOHandler.ReadLn;

  comando := Copy(Texto, 1, Pos('||', Texto)-1);

  if comando = 'listaSuporte' then

  begin

    ListaSuporte := GetUsuariosSuporte;

    AContext.Connection.IOHandler.WriteRFCStrings(ListaSuporte);

  end;

end;

Listagem 3. Obtendo a lista de suporte

function TServerF.GetUsuariosSuporte: TStringList;

var

 ListaSuporte: TStringList;

begin

  dtm.qrySuporte.Close;

  dtm.qrySuporte.SQL.Clear;

  dtm.qrySuporte.SQL.Add('SELECT * FROM SUPORTE');

  dtm.qrySuporte.Open;

  ListaSuporte := TStringList.Create;

  while not dtm.qrySuporte.Eof do

  begin

    ListaSuporte.Append('listaSuporte||' +

      dtm.qrySuporteID_SUPORTE.asString + '||' +

      dtm.qrySuporteAPELIDO.AsString + '||'+

      dtm.qrySuporteONLINE.AsString + '||'+

      dtm.qrySuporteSTATUS.AsString+ '||');

    dtm.qrySuporte.Next;

  end;

  dtm.qrySuporte.Close;

  result := ListaSuporte;

end;

Mais tarde veremos que a lista retornada será utilizada para armazenar esses integrantes do suporte na versão client e para isso vamos utilizar o que o Delphi nos oferece de melhor, um ClientDataSet.

Mas antes disso veremos outro ponto importantíssimo em nossa solução. Para que as três aplicações conversem entre si, vamos estabelecer um protocolo. Definiremos algumas regras e comandos que farão com que os três aplicativos enviem e recebam mensagens e comandos entre si.

No servidor é preciso agora verificar se o usuário que está tentando se conectar é válido. Toda vez que algum usuário tenta se conectar utilizando o host e porta específicos, o evento onConnect do TcpServer é disparado e é nele que vamos verificar as informações que estão chegando. Programe o evento OnConnect mencionado anteriormente conforme a Listagem 4.

Listagem 4. Validando a conexão

procedure TServerF.tcpServerConnect(AContext: TIdContext);

var

  cliente: TUsuario;

  loginInfo: TLoginInfo;

  IdUsuario: integer;

  texto, comando: string;

begin

  texto := AContext.Connection.IOHandler.ReadLn;

  comando := Copy(texto,1,pos('||',texto)-1);

  delete(texto,1,pos('||',texto)+1);

  loginInfo := TLoginInfo.Create;

  loginInfo.IdEmpresa := strToInt(Copy(texto,1,pos('||',texto)-1));

  delete(texto,1,pos('||',texto)+1);

  loginInfo.Nick := Copy(texto,1,pos('||',texto)-1);

  delete(texto,1,pos('||',texto)+1);

  loginInfo.Senha := Copy(texto,1,pos('||',texto)-1);

  idUsuario := Login(loginInfo.IdEmpresa, loginInfo.Nick, loginInfo.Senha);

  if idUsuario > 0  then

  begin

    cliente := TUsuario.Create;

    cliente := LoadUsuario(idUsuario);

    cliente.IP := AContext.Connection.Socket.Binding.PeerIP;

    cliente.Host := GStack.HostByAddress(cliente.IP);

    cliente.Online := oSim;

    cliente.Status := sDisponivel;

    AContext.Data := cliente;

    AContext.Connection.IOHandler.WriteLn('sucessoLogin||'

      + IntToStr(cliente.IdUsuario)+ '||');

   Total := Total + 1;

  end

  else

  begin

    AContext.Connection.IOHandler.WriteLn('sucessoLogin||'

      + IntToStr(idUsuario)+'||');

    Acontext.Connection.Disconnect;

  end;

  loginInfo.Free;

end;

 

Vamos entender a Listagem 4. Observe que o evento tcpServerConnect carrega consigo um parâmetro do tipo TIdContext, o AContext. Para cada client que se conecta, ou tenta se conectar, um novo TIdContext é criado o representando. A variável AContext contém informações importantes, como por exemplo a conexão atual, e através dela podemos obter os comandos que o client está enviando.  Isso é feito na linha a seguir:

 

texto := AContext.Connection.IOHandler.ReadLn;

 

Perceba que em dado momento, recebemos o ID do usuário na variável idUsuario. Essa variável recebe a identificação do usuário através da função Login.

 

  idUsuario := Login(loginInfo.IdEmpresa, loginInfo.Nick, loginInfo.Senha);

 

Veja que desmembramos o conteúdo da variável texto em um objeto da classe TLoginInfo e passamos o resultado para o método Login, que por sua vez retorna o Id do usuário, 0 se o login não for válido ou ainda -1 se o usuário já estiver conectado. Caso o usuário seja válido, um objeto do tipo TUsuario é instanciado e passado para a propriedade Data do objeto AContext atual, então enviamos o retorno disso de volta para o client, que digo novamente é representado pelo objeto AContext. Atualizamos a quantidade de usuários conectados no server.

 

Nota: A definição da classe TLoginInfo e TUsuario é feita na Unit ClientesU e por questões de espaço não é exibida aqui mas está disponível para download.

 

Crie uma nova função no Server para que possamos fazer esse trabalho. Veja seu código na Listagem 5. Na Listagem 5 vemos como que um usuário é validado. Criamos uma function chamada Login que é chamada de dentro do evento OnConnect retornando o ID do usuário. A validação é ainda mais simples do que vimos até agora. Apenas pegamos a Empresa, Apelido e Senha do usuário e efetuamos consultas ao banco de dados.

Listagem 5. Verificando o usuário junto ao banco de dados

function TServerF.Login(IdEmpresa: integer; Nick, Senha: string): Integer;

begin

  dtm.qryUsuario.Close;

  dtm.qryUsuario.SQL.Clear;

  dtm.qryUsuario.SQL.Add('SELECT * FROM USUARIO');

  dtm.qryUsuario.SQL.Add('WHERE ID_EMPRESA = :ID_EMPRESA');

  dtm.qryUsuario.SQL.Add('AND APELIDO = :NICK');

  dtm.qryUsuario.SQL.Add('AND SENHA = :SENHA');

  dtm.qryUsuario.ParamByName('NICK').AsString := Nick;

  dtm.qryUsuario.ParamByName('ID_EMPRESA').AsInteger := IdEmpresa;

  dtm.qryUsuario.ParamByName('SENHA').AsString := Senha;

  dtm.qryUsuario.Open;

  if dtm.qryUsuario.IsEmpty then

    result := 0

  else

  begin

    if dtm.qryUsuarioONLINE.AsInteger = 1 then

      result := -1

    else

    begin

      result := dtm.qryUsuarioID_USUARIO.AsInteger;

      dtm.qryUsuario.Edit;

      dtm.qryUsuarioONLINE.AsInteger := 1;

      dtm.qryUsuarioSTATUS.AsInteger := 1;

      dtm.qryUsuario.Post;

      dtm.qryUsuario.Transaction.CommitRetaining;

    end;

  end;

  dtm.qryUsuario.Close;

end;

Definindo um protocolo

Como o client precisa se comunicar com o servidor é necessário que criemos um protocolo de comunicação que seja obedecido e conhecido pelas partes envolvidas. Um protocolo de comunicação é um conjunto de comandos e valores que são formatados em um padrão pré-determinado. Nosso protocolo será simples, baseado em texto simples e cobrirá as funcionalidades citadas anteriormente. Veja que nas Tabelas 1 e 2 especificamos os o comandos válidos que serão trocados entre aplicação cliente e servidora, respectivamente.

Funcionalidade

Protocolo

Login

login||IdEmpresa||<Nick>||senha||

Obter lista dos usuários de suporte

listaSuporte||IdUsuario||

Enviar mensagem para alguém do suporte

mensagemParaSuporte||IdUsuarioOrigem||IdSuporte||mensagem||NumeroLinha

Estou escrevendo

Escrevendo||IdUsuarioOrigem||IdUsuarioDestino||

Parei de escrever

pareiEscrever||IdUsuarioOrigem||IdUsuarioDestino||

Mudança de estado

mudancaEstado||IdUsuario||Estado||OnLine||

Obter a lista dos clientes cadastrados e seus status

listaCliente||<IdSuporte>||

Login do client de suporte

loginSuporte||<Nick>||senha||

Mensagem para algum cliente

mensagemParaCliente||IdSuporteOrigem||IdCliente||mensagem||NumeroLinha

Tabela 1. Comandos do protocolo

 

Funcionalidade

Protocolo

Retornar a lista de suporte

Um stringlist onde cada item obedece:

listaSuporte||idSuporte||Nick||OnLine||estado||

Sucesso/Falha de conexão/login

sucessoLogin||IdCliente||

Tabela 2. Comandos de retorno do server

Basicamente nosso protocolo é composto por um comando e seus parâmetros, todos em um texto. Para que possamos distinguir o que é cada valor, criamos um separador para nos ajudar. Nosso separador é composto por dois pipe-lines, ||, colocados em seqüência. Para que possamos ver o protocolo em funcionamento precisamos implementar tanto o client quanto o server em paralelo. Já vimos um exemplo de uso do protocolo na Listagem 2. Veja:

 

  if comando = 'listaSuporte' then

  begin

    ListaSuporte := GetUsuariosSuporte;

    AContext.Connection.IOHandler.WriteRFCStrings(ListaSuporte);

  end;

 

Aqui estamos verificando se o comando recebido é a palavra listaSuporte e caso seja, carregamos os usuários como já vimos anteriormente.

Criando a aplicação cliente

Teremos dois clients. Um será utilizado por nossos clientes e outro pela equipe de suporte que terá funcionalidades especiais como veremos mais adiante. Vamos começar pelo dos clientes. Adicione à solução um novo projeto, do tipo Win32, chamando-a de DevChatClient.dproj. No formulário principal vamos adicionar um TIdTCPClient da guia Indy Clients e configurar suas propriedades Port e Host para que sejam as mesmas utilizadas pelo servidor de mensagens. Em Host colocamos o IP da máquina onde está o servidor de mensagens, que neste caso é 127.0.0.1 ou localhost. Adicione agora um painel e nele insira três TEdit e um TButton, como na Figura 5.

No OnClick do botão tentamos conectar ao servidor de mensagens, como é mostrado na Listagem 6.

Figura 5. Tela inicial do Client

Listagem 6. Conectando no servidor

procedure TClientF.btConectarClick(Sender: TObject);

begin

  if (Trim(EditEmpresa.Text) <> '') and

    (Trim(EditLogin.Text) <> '') and

    (Trim(EditSenha.Text) <> '') then

  begin

    tcClient.Connect;

    tcClient.Socket.WriteLn('login||'+ EditEmpresa.Text +

      '||' + EditLogin.Text + '||'+ EditSenha.Text+'||');

  end

  else

  begin

    ShowMessage('Preencha todas as informações para Login');

    EditEmpresa.SetFocus;

  end;

end;

Perceba que o código não é em nada complexo. Estamos verificando se os campos Empresa, Login e Senha estão preenchidos e em caso positivo chamamos o método Connect do componente TCPClient. Esse método se encarregará de conectar-se à aplicação server que deverá estar em execução. Após isso, enviamos a mensagem login||NomeDaEmpresa||Login||Senha|| (Veja a utilização do nosso protocolo novamente). Essas informações fazem parte do protocolo que criamos, ou seja, nada mais são do que um simples texto que será recebido na aplicação Server e conseqüentemente validado.

Depois de conectado ao servidor, nossa aplicação deverá listar os membros da equipe de suporte e verificar o status de cada um. Lembre-se qie para isso programamos no Server uma função capaz de nos retornar tal informação. O retorno da função é um TStringList, ou seja, uma lista de strings. Como já foi dito, usaremos um ClientDataset para preencher com esse membros.  

Adicione então um ClientDataSet no formulário principal do Client e adicione os campos que representam um usuário do suporte: IdSuporte, Nick, OnLine e Status. Adicione também um DataSource e ligue-o ao ClientDataSet. Agora adicione um DBGrid, pois é nele que exibiremos a lista de suporte.

Para que o client processe o retorno é necessário criar duas classes como na Listagem 7, para que além de tratar as mensagens recebidas a resposta do client seja mantida.

Listagem 7. Classes que mantém a responsividade

  TReadingThread = class(TThread)

    protected

      FConn: TIdTCPConnection;

      procedure Execute; override;

    public

      constructor Create(AConn: TIdTCPConnection); reintroduce;

  end;

 

  TLog = class(TIdSync)

    protected

      FMsg: string;

      procedure DoSynchronize; override;

    public

      constructor Create(const AMsg: string);

      class procedure AddMsg(const AMsg: string);

  end;

Basicamente essas duas classes trabalham juntas na leitura de qualquer mensagem enviada pelo servidor. Elas asseguram que a thread principal da VCL não será utilizada quando as mensagens chegam, dessa forma a interface gráfica fica livre para que o usuário a utilize. A classe TReadingThread lê a mensagem primeiro e então utiliza a classe TLog para processá-la. Antes de podermos utilizar as classes TLog e TReadingThread, vamos implementá-las como na Listagem 7.

 

ClubeDelphi PLUS!

Acesse agora o mesmo o portal do assinante ClubeDelphi e assista a uma vídeo aula de Guinther Paulo que mostra como trabalhar com thread.  

http://www.devmedia.com.br/articles/viewcomp.asp?comp=1733

 

Nota DevMan!

O que são Thread?

         Um programa normal é chamado de single thread, ou seja, processo simples ou processo único. Isso significa que só há processamento em um determinado ponto do programa como a emissão de um relatório, cadastramento de um cliente ou mesmo manipulação de qualquer tipo de informação.

         Uma thread é um processo que acontece com início, meio e fim, e também acontece em um determinado momento dentro de um sistema. Porém, quando temos mais de um processo dentro do mesmo programa chamamos isso de multithread. Multithread porque temos mais de um processo acontecendo ao mesmo tempo, como emissão de um relatório e cadastramento de um cliente ao mesmo tempo.

         As threads são usadas normalmente para agilizar processos ou criar vários processos iguais, porém trabalhando em momentos diferentes. Por exemplo: podemos solicitar a emissão de um relatório grande que levará 2 horas para ser emitido pelo programa devido a massa de dados que ele irá manipular. Se desenvolvermos um relatório como esse em nosso sistema e o usuário utilizá-lo, certamente serão 2 horas sem poder utilizar o programa caso seja single thread.

         A partir de momento que o isolamos em uma thread, temos condições de liberar o programa para que ele execute novas tarefas sem precisar aguardar o término da primeira.

         Um exemplo bastante simples de se entender é quando estamos utilizando programas especializados em downloads de arquivos, como o antigo Kazaa ou o mais recente LimeWire para download de músicas. Imagine se cada música que você ordenasse o programa a baixar você tivesse que aguardar a última música ser totalmente baixada? Levaria muito tempo. Portanto, esses programas costumam ser multithread, pois executam mais de um processo ao mesmo tempo de forma independente.

 

Crie essas duas classes na Unit do formulário principal da versão client. Basta digitar suas declarações abaixo da palavra reservada Type e em seguida pressionar Ctrl + Shift + C para que o Delphi nos crie o escopo principal de cada método. Um método DoSynchronize fará o papel de sincronizar as mensagens entre as aplicações independente do que ocorrendo no restante do aplicativo.

Precisamos também adicionar na procedure Synchronize o tratamento do retorno da lista. Parte do código dessa procedure pode ser visto na Listagem 8. Evidentemente a listagem não foi totalmente impressa no artigo devido ao seu tamanho. É nesse procedimento que prevemos e tratamos todas as mensagens enviadas e recebidas do servidor. Por exemplo: quando o nosso suporte técnico enviar uma mensagem ao cliente, essa mensagem será recebida com a string mensagemParaCliente. Encontre no trecho de código da Listagem 8 esse pedaço do código e veja o que fazemos.

Listagem 8. Procedure Synchronize no Client

procedure TLog.DoSynchronize;

var

  comando: string;

  RetornoLogin: integer;

  CodigoSuporte: integer;

  NickSuporte: string;

  OnLineSuporte: integer;

  StatusSuporte: integer;

  Mensagem: string;

  NumeroLinha: integer;

begin

  comando := Copy(FMsg,1,pos('||',FMsg)-1);

  if comando = 'sucessoLogin' then

  begin

    delete(FMsg,1,pos('||',FMsg)+1);

    RetornoLogin:= strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    case RetornoLogin of

      -1 : Form1.JaEstavaConectado;

       0 : Form1.LoginDeuErrado;

       else

       begin

         Form1.IdCliente := RetornoLogin;

         Form1.LoginDeuCerto;

       end;

    end;

    exit;

  end;

  if comando = 'listaSuporte' then

  begin

    delete(FMsg,1,pos('||',FMsg)+1);

    CodigoSuporte := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    delete(FMsg,1,pos('||',FMsg)+1);

    NickSuporte := Copy(FMsg,1,pos('||',FMsg)-1);

    delete(FMsg,1,pos('||',FMsg)+1);

    OnLineSuporte := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    delete(FMsg,1,pos('||',FMsg)+1);

    StatusSuporte := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    Form1.AtualizaListaSuporte(CodigoSuporte, NickSuporte, OnLineSuporte, StatusSuporte);

  end;

  if comando = 'mensagemParaCliente' then

  begin

    delete(FMsg, 1, Pos('||', FMsg)+1);

    CodigoSuporte := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    delete(FMsg,1,pos('||',FMsg)+1) ;

    delete(FMsg,1,pos('||',FMsg)+1) ;

    Mensagem := Copy(FMsg,1,pos('||',FMsg)-1);

    delete(FMsg,1,pos('||',FMsg)+1);

    NumeroLinha := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    Form1.IniciaChatCom(CodigoSuporte,Mensagem, NumeroLinha);

  end;

[...]

end;

 

Basicamente, apagamos os pipe-lines de nosso protocolo, pegamos o código do suporte, a mensagem e o número dela e fazemos o start da conversa chamando o método IniciaChatCom.

 

Nota: A implementação da função IniciaChatCom pode ser vista no arquivo de download disponível no site de edição no portal DevMedia.

 

Toda a procedure DoSynchronize funciona dessa forma. O mais interessante é que podemos implementar diversas funcionalidades somente alterando nosso protocolo e prevendo as mensagens em ambas aplicações, Server e Client.

A classe TReadingThread possui apenas dois métodos: Create e Execute. No Create apenas inicializamos a variável FConn e herdamos o método Create de seu ancestral. Já o método Execute, esse é encarregado de escutar a aplicação e detectar o exato momento de uma conexão. Caso uma conexão seja interceptada, então uma mensagem é gerada e enviada para procedure AddMsg da classe TLog. Isso é necessário para que possamos ter controle de tudo que é feito no sistema. O código de implementação dos métodos das classes TReadingThread e TLog podem ser vistos na Listagem 9.

Note que o método AddMsg cria uma instância da classe e sincroniza. O construtor Create apenas atualiza a variável FMsg e herda o método Create de seu ancestor. Por fim o método DoSynchronize, que é responsável por fazer o sincronismo das mensagens funcionar perfeitamente. Recebmos o comando e avaliamos. Em caso de sucesso, apagamos os caracteres “||” (pipe-line) e testamos o retorno que pode ser 0 ou 1.

Quero destacar que a classe TLog é o núcleo do client, visto que ela é quem irá processar as mensagens recebidas e irá tomar as devidas providências para que a solicitação/resposta seja feita de acordo.

Para utilizar essas classes vamos adicionar uma variável na seção Var do formulário que referencie a classe TReadingThread, como a seguir:

 

var

  Form1: TForm1;

  rt: TReadingThread = nil;

 

E então no evento onConnected do TIdTCPClient instanciamos essa thread:

 

  rt := TReadingThread.Create(tcClient);

 

E ao encerrar a conexão é preciso também encerrar a Thread de leitura, isso é feito no evento onDisconnected, como é mostrado a seguir:

 

  if rt <> nil then

  begin

    rt.Terminate;

    rt.WaitFor;

    FreeAndNil(rt);

  end;

 

Observe na Listagem 8 que na procedure TLog. DoSynchronize estamos lidando com o retorno do servidor e se a conexão for bem sucedida chamamos o método LoginDeuCerto da classe TForm1, que é o formulário. Agora ao vermos o que esse método faz, vemos que o client envia outra mensagem para o servidor, solicitando a lista de suporte. A mensagem e enviada usando o método WriteLn:

 

  PainelLogin.Visible := false;

  Caption := 'DevChat - '+ EditLogin.Text;

  tcClient.Socket.WriteLn('listaSuporte||'+ IntToStr(IdCliente));

 

Listagem 9. Implementando TReadingThread e TLog

constructor TReadingThread.Create(AConn: TIdTCPConnection);

begin

  FConn := AConn;

  inherited Create(False);

end;

procedure TReadingThread.Execute;

begin

  while not Terminated and FConn.Connected do

  begin

    TLog.AddMsg(FConn.IOHandler.Readln);

  end;

end;

{ TLog }

class procedure TLog.AddMsg(const AMsg: string);

begin

  with Create(AMsg) do

    try

       Synchronize;

    finally

       Free;

    end;

end;

constructor TLog.Create(const AMsg: string);

begin

  FMsg := AMsg;

  inherited Create;

end;

 

Quem efetivamente faz a atualização da lista de membros do suporte técnico é a função AtualizaListaSuporte apenas verifica se o código do usuário de suporte passado como parâmetro já existe no ClientDataset, se não existir ele é adicionado. Veja na Listagem 10.

Listagem 10. Código da função AtualizaListaSuporte

procedure TForm1.AtualizaListaSuporte(CodigoSuporte:

  integer; NickSuporte: string; OnLineSuporte,

  StatusSuporte: integer);

begin

  if cdsSuporte.Locate('idSuporte',CodigoSuporte,[]) then

     cdsSuporte.Edit

  else

    cdsSuporte.Insert;

  cdsSuporteidSuporte.AsInteger := CodigoSuporte;

  cdsSuporteNick.AsString := NickSuporte;

  cdsSuporteOnline.AsInteger := OnLineSuporte;

  cdsSuportestatus.AsInteger := StatusSuporte;

  cdsSuporte.Post;

end;

Montando a aplicação do suporte

As duas aplicações cliente são bastante semelhantes, diferindo apenas em alguns detalhes e funcionamentos. Por isso podemos clonar a aplicação criada no tópico anterior e fazer pequenas modificações. Adicione um novo projeto Win32 ao grupo e deixe o formulário principal semelhante o formulário do client destinado aos clientes, com a exceção que não teremos nele o TEdit referente o código da empresa. Crie também as classes TReadingThread e TLog, como foi feito no client dos clientes.

A primeira coisa que devemos pensar é que precisamos carregar a lista de clientes em nossa aplicação. Ao se conectar é preciso obter a lista dos clientes cadastrados em nosso banco de dados, para isso vamos utilizar o comando listaCliente do nosso protocolo de comunicação. O server ao receber esse comando executa uma instrução SQL no BD que retorna todos os clientes cadastrados no serviço de mensagens, incluindo nas informações o status de cada um e devolve isso ao client do suporte. Esse por sua vez trata a mensagem no método DoSynchronize da classe TLog, como é visto na Listagem 11.

Listagem 11. Trecho de DoSynchronize para tratamento da lista de clientes

[…]

  if comando = 'listaCliente' then

  begin

    delete(FMsg,1,pos('||',FMsg)+1);

    CodigoCliente := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    delete(FMsg,1,pos('||',FMsg)+1);

    NickCliente := Copy(FMsg,1,pos('||',FMsg)-1);

    delete(FMsg,1,pos('||',FMsg)+1);

    OnLineCliente := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    delete(FMsg,1,pos('||',FMsg)+1);

    StatusCliente := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    DevChatSuporteF.AtualizaListaCliente(CodigoCliente, NickCliente, OnLineCliente, StatusCliente);

  end;

[...]

 

Veja que é bastante semelhante ao que vimos no aplicativo destinado aos clientes. A diferença principal é que estamos chamando o método AtualizaListaCliente que por sua vez se utiliza de um ClientDataSet para armazenar os clientes retornados, seguindo o padrão empregado no client dos clientes.

 

Nota: Para que o client de suporte se conecte é utilizado o comando loginSuporte do protocolo que realiza as conferências necessárias para saber se o usuário de suporte é válido ou não.

 

Se executarmos a aplicação nesse momento, rodando primeiramente o servidor e posteriormente os dois clientes, veremos que em cada uma teremos a lista de usuários/suporte necessários. Porém como faremos para notificar a mudança de estado, já que mesmo estando conectados os usuários representados nas listas ainda estão off-line?

Vamos utilizar o comando mudancaEstado. Nele informamos o Id do usuário, não importando se é cliente ou suporte, seguidos do seu status e se está on-line. O server por sua vez, ao receber essa mensagem, realiza uma operação de update no banco de dados, logicamente para registrar a mudança de estado e então encaminha para todos  a mensagem de que o determinado usuário mudou seu status. Veja Listagem 12.

Listagem 12. Trecho de DoSynchronize com mudança de status

[...]

if comando = 'mudancaEstado' then

 begin

    delete(Texto,1,pos('||',Texto)+1);

    CodigoUsuario := strToInt(Copy(Texto,1,pos('||',Texto)-1));

    delete(Texto,1,pos('||',Texto)+1);

    StatusUsuario := strToInt(Copy(Texto,1,pos('||',Texto)-1));

    delete(Texto,1,pos('||',Texto)+1);

    OnLineUsuario := strToInt(Copy(Texto,1,pos('||',Texto)-1));

    NotificarMudancaEstado(AContext,codigoUsuario,TStatus(StatusUsuario),TOnLine(OnlineUsuario));

 end;

[…]

 

Nota: Como foi visto no início, os clientes e suporte ficam armazenados em tabelas distintas, então para evitar conflitos de Id, ambas as tabelas poderiam utilizar o mesmo generator ou a tabela de clientes poderia iniciar a contagem de Ids a partir do número 1000.

 

Nota DevMan!

O que são Generators’s?

         O Generator um recurso existente no banco de dados Firebird capaz de gerar número seqüenciais. Quem chegou a utilizar os bancos de dados Paradox e DBase conhece esse recurso como auto-numeração.  Havia um tipo de campo nesses bancos de dados que fazia a geração automática de números seqüenciais que normalmente são usado para evitar duplicação de registros da base.

         Com a entrada do Firebird e Interbase ao mercado, esse conceito morreu dando lugar aos Generator’s. No Firebird 2.0 o Generator foi substituído pelo recurso Sequence, que tem a mesma finalidade.

 

A procedure DoSynchronize consegue saber o estado de um usuário por que fica o tempo todo escutando as mensagens que são transmitidas e recebidas entre Server e Client. Em outras palavras, cada client ao receber o comando mudancaEstado, atualiza o estado do usuário, como é mostrado na Listagem 13 e na Figura 6.

Listagem 13. Atualizando o status do usuário ao receber comando

procedure TDevChatSuporteF.AtualizaEstado(CodigoCliente, Estado,

  Online: integer);

begin

  if CodigoCliente = IdSuporte then exit;

  cdsClientes.First;

  if cdsClientes.Locate('idCliente',CodigoCliente,[]) then

  begin

    cdsClientes.Edit;

    cdsClientesOnline.AsInteger := OnLine;

    cdsClientesStatus.AsInteger := Estado;

    cdsClientes.Post;

  end

  else

  begin

    tcClient.Socket.WriteLn('listaCliente||'+ IntToStr(IdSuporte));

  end;

end;


Figura 6. Mudança de status em execução


Desconectando do servidor

No evento onClose dos clients disparamos o método   tcClient.Disconnect que por sua vez dispara no server o evento onDisconect, indicando que um client foi desconectado, dessa forma nesse evento podemos mudar o estado desse cliente para off-line e notificamos os outros que ainda estão conectados, como é visto a seguir:

 

Total := Total - 1;

SetStatusUsuario(TUsuario(AContext.Data).IdUsuario,TStatus(0),

  TOnLine(0),TUsuario(AContext.Data).Suporte);

if Total > 0 then

 NotificarMudancaEstado(AContext,TUsuario(AContext.Data).IdUsuario,

  TStatus(0),TOnLine(0));

 

Trocando mensagens

Após termos o controle de quem está ou não está conectado é preciso realizar a troca de mensagens entre clients. Antes de tudo é preciso entender como o chat se dará. O cliente dará um clique duplo sobre o usuário de suporte que está on-line e então a janela de chat aparece. Caso o cliente dê um clique duplo em um usuário de suporte com uma conversa já iniciada, a janela onde essa conversa está sendo mantida é então exibida e não será criada uma nova. Tendo essas premissas, vemos que é preciso então criar uma forma de controlar quais janelas que já estão abertas.

Para tal começaremos com o client dos clientes. Adicione a esse projeto um novo formulário, salve-o como ChatU.pas e deixe-o como na Figura 7. E no evento onClick do botão Enviar  disparamos o comando de envio de mensagens (Listagem 14).


Figura 7. Formulário de conversação

Listagem 14. Código do evento OnClick do botão Enviar

procedure TChatF.btEnviarClick(Sender: TObject);

var

  I: integer;

begin

  for I := 0 to memoTexto.Lines.Count - 1 do

    Form1.tcClient.Socket.WriteLn('mensagemParaSuporte||'+   

    IntToStr(Form1.IdCliente)+'||'+IntToStr(Self.Tag)+

    '||'+(memoTexto.Lines[I])+'||'+IntToStr(I)+'||');

  memoChat.Lines.Append(DateTimeToStr(now) + ' - Você diz: ' +

    MemoTexto.Text);

  memoTexto.Clear;

  memoTexto.SetFocus;

end;

Perceba que é simples. Apenas enviamos a mensagem para o componente TCPClient usando o método WriteLn. Em seguida atualizamos o componente Memo com informações de data e hora da mensagem.

Crie uma propriedade pública, de nome NomeSuporte e  no seu método Set mude o Caption da janela como segue:

 

  FNomeSuporte := Value;

  Caption := 'Conversando com : ' + Value;

 

Vamos adicionar um campo privado no formulário principal do client, um campo de nome ListaJanelas que será do tipo TList. No evento onCreate do formulário instanciamos esse campo. Essa lista irá armazenar as conversas que estão em andamento.

 

ListaJanelas := TList.Create;

 

Para verificar se já existe uma conversa em andamento vamos realizar um truque aqui. Utilizaremos a propriedade Tag do TForm para armazenar o id do suporte com quem se está conversando e vamos procurar por esse id na lista de janelas. Se o id não constar criamos um novo formulário, caso contrário retornamos a instância encontrada. Tudo isso pode ser visto na Listagem 15. Esse método é então utilizado no evento OnDblClick do DBGrid de usuários de suporte disponíveis.

Listagem 15. Controlando quais conversas já estão iniciadas

function TForm1.CriaFormChat(ParaIdSuporte: integer): TChatF;

var

 x: integer;

 encontrada: boolean;

 Form : TChatF;

begin

  encontrada := false;

  for x := 0 to ListaJanelas.Count - 1 do

  begin

    if TForm(ListaJanelas.Items[x]).Tag = ParaIdSuporte then

    begin

      encontrada := true;

      result := TChatF(ListaJanelas.Items[x]);

      break;

    end;

  end;

  if not encontrada then

  begin

    Form := TChatF.Create(Self);

    Form.Tag := ParaIdSuporte;

    ListaJanelas.Add(Form);

    result := Form;

  end;

end;

O método IniciaChatCom localiza o registro do usuário de suporte com quem estamos tentando falar e verifica se ele já possui uma conversa em aberto conosco, se possuir a janela de conversação é exibida. Quando o parâmetro Mensagem possui algum valor ele é então exibido na janela de conversação. Vale lembrar aqui que a mensagem que será exibida é enviada primeiramente ao Server e que ele é que encaminha para o destinatário.

O comando é desmembrado para suas respectivas partes e então o método EnviaMensagemParaSuporte é utilizado. Esse método tem a tarefa de percorrer a lista de clients conectados e identificar quem deve receber a mensagem e então passá-la. Na Listagem 16 vemos isso. Essa lista de clients é retornada pelo método LockList de TIdContext.

Listagem 16. Enviando a mensagem para o Client correto

procedure TServerF.EnviaMensagemParaSuporte(const AContext:

  TIdContext; idUsuarioOrigem, idSuporte: integer;

  Mensagem: string; Linha: integer);

var

 List: TList;

 I: integer;

 HackContext : TContextHack;

begin

  List := TContextHack(AContext).FContextList.LockList;

  try

    for I := 0 to List.Count - 1 do

    begin

      HackContext := TContextHack(List[i]);

      if TUsuario(HackContext.Data).IdUsuario = idSuporte then

      begin

         HackContext.Connection.IOHandler.WriteLn('mensagemParaSuporte||'+

           IntToStr(idUsuarioOrigem)+'||'+IntToStr(idSuporte)+

           '||'+Mensagem+'||'+IntToStr(Linha)+'||');

         break;

      end;

    end;

  finally

    TContextHack(AContext).FContextList.UnlockList;

  end;

end;

Recebendo uma mensagem

Podemos dizer agora que nossa solução está quase pronta. Precisamos ver o último passo, receber e exibir as mensagens. Ao receber um comando MensagemParaCliente é preciso incluir no método TLog.DoSynchronize o respectivo tratamento. Observe na Listagem 17 que utilizamos o método IniciaChatCom passando todos os parâmetros,  o que fará com que a mensagem recebida seja exibida corretamente. E novamente não há grande complexidade no código. Novamente desmembramos a mensagem recebida e extraímos dela o comando recebido. Por fim chamamos a função IniciaChatCom.

Listagem 17. Recebendo uma mensagem

  if comando = 'mensagemParaCliente' then

  begin

    delete(FMsg,1,pos('||',FMsg)+1);

    CodigoSuporte := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    delete(FMsg,1,pos('||',FMsg)+1) ;

    delete(FMsg,1,pos('||',FMsg)+1) ;

    Mensagem := Copy(FMsg,1,pos('||',FMsg)-1);

    delete(FMsg,1,pos('||',FMsg)+1);

    NumeroLinha := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    Form1.IniciaChatCom(CodigoSuporte,Mensagem, NumeroLinha);

  end;

Avisando que você está digitando

Um recurso interessante é avisar para ambas as parte, de que a pessoa na outra ponta está digitamdo algo, assim como o MSN. Para implementar isso, vamos fazer uso do comando Escrevendo ou PareiEscrever definidos anteriormente no protocolo de comunicação.

Vamos adicionar no formulário de chat um TStatusBar e no evento OnChange do TMemo de digitação nós verificamos se existe texto ainda no TMemo, se existir verificamos se já não enviamos o comando Escrevendo. Se o comando ainda não foi enviado então o enviamos. Caso o TMemo não tenha texto algum, enviamos o comando PareiEscrever, confira tudo isso na Listagem 18. Quando o client recebe esses comandos ele apenas limpa ou adiciona o texto informando isso no TStatusBar (Listagem 19).

Listagem 18. Avisando da digitação

procedure TChatF.memoTextoChange(Sender: TObject);

begin

  if Trim(memoTexto.Text) = '' then

  begin

    FDigitando := false;

    Form1.tcClient.Socket.WriteLn('pareiEscrever||'+

      IntToStr(Form1.IdCliente)+'||'+

      IntToStr(Self.Tag)+'||');

    exit;

  end;

  if not FDigitando then

  begin

    FDigitando := true;

    Form1.tcClient.Socket.WriteLn('Escrevendo||'+

      IntToStr(Form1.IdCliente)+'||'+

      IntToStr(Self.Tag)+'||');

    exit;

  end;

end;

Listagem 19. Tratando comando Escrevendo/pareiEscrever

procedure TLog.DoSynchronize;

var

{...}

begin

{...}

  if comando = 'Escrevendo' then

  begin

    delete(FMsg,1,pos('||',FMsg)+1);

    CodigoSuporte := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    Form1.SuporteEscrevendo(CodigoSuporte,true);

  end;

  if comando = 'pareiEscrever' then

  begin

    delete(FMsg,1,pos('||',FMsg)+1);

    CodigoSuporte := strToInt(Copy(FMsg,1,pos('||',FMsg)-1));

    Form1.SuporteEscrevendo(CodigoSuporte,false);

  end;

end;

procedure TForm1.SuporteEscrevendo(idSuporte: integer; Escrevendo: boolean);

begin

  cdsSuporte.Locate('IdSuporte',idSuporte,[]);

  with CriaFormChat(idSuporte) do

  begin

    if Escrevendo then

      BarraMensagem.SimpleText := cdsSuporteNick.AsString + ' está escrevendo...'

    else

      BarraMensagem.SimpleText := '';

  end;

end;

Por fim, será necessário fazer a implementação do que acabamos de ver, na versão client de uso do suporte técnico. Ele é muito similiar ao Client destinado para o clientes. Existem algumas diferenças como, por exemplo, a de ao invés de listar os usuários de suporte, os clientes é que são listados.

Na seção de download dessa edição, será possível e analisar o código-fonte completo dessas aplicações.

Conclusão

Este artigo foi apenas uma introdução no desenvolvimento de um Messenger. A partir do que foi mostrado, novos recursos podem ser explorados como envio de arquivos, avisos de chamada de atenção. No server um log e histórico poderia ser montado além de ter a possibilidade de convertê-lo para um serviço do Windows. Fazer tudo isso exigiria várias edições, porém estou à disposição quando alguma dúvida surgir. Muito obrigado.


Paulo Quicoli
Editor Geral da revista ClubeDelphi. Formado em processamento de dados pela FATEC-TQ. Atua como Analista de Sistemas na Siplan Control-M unidade Jaboticabal (www.siplancontrolm.com.br), prof. na FATEC-TQ e consultor na NHibernate Brasil (www.nhibernatebrasil.net) Blog:http://quicoli.wordpress.com
O que você achou deste post?

    43 COMENTÁRIOS

[Fechar]

Este post é fechado - você precisa ter acesso ao post para incluir um comentário.



Juan Gonzales Torres
Muio bom o artigo. Preciso enviar e reber a imagem da webcam, teria alguma ideia. Obrigado.
[há +1 ano] - Responder

 

Giulian Fagundes Paixão
Boa tarde!

Gostaria de parabenizá-los pelo excelente artigo! Realmente bastante útil para todos que trabalham com desenvolvimento e suporte técnico aos clientes.

Baixei os fontes e fiz pequenas alterações visuais para poder adaptá-lo ao meu aplicativo. Só que está acontecendo o seguinte problema tanto na minha "versão" quanto na "versão" original baixada do site:

Tenho 2 clientes: A e B
Conecto o cliente A
Conecto o cliente B
Quando faço a conexão do cliente B ele atualiza o status de ambos: aparecem na relação de clientes online
Se desconecto o cliente B (que foi o último a se conectar) ele atualiza normalmente o cliente A e aparece offline
Se desconecto o cliente A (que foi o primeiro a se conectar) não atualiza o cliente B e continua o clente A online

Não sei se fui claro na exposição do problema...

Espero contar com a ajuda de vocês.

Obrigado
[há +1 ano] - Responder

 

Gilsonnunes
GetUsuariosSuporte cria e retorna um TStringList quem destroi eles?
[há +1 ano] - Responder

 

[autor] Paulo Quicoli
Como vc pode ver, a instância criada é passada como retorno do método (Listagem 2):
 
     ListaSuporte := GetUsuariosSuporte;

    AContext.Connection.IOHandler.WriteRFCStrings(ListaSuporte);

 
Assim, acredito que após usá-la no método WrteRFCStrings, vc pode dar um FreeAndNil nela.
 
Abraço
[há +1 ano] - Responder
 

Mauricio Jr
Alguem pode me mandar no meu email para devidas modificaçoes?
se nao for muito incomodo, é porque estou muito sem tempo para fazê-lo mesmo, se alguem tiver esse programa ae e puder me ajudar ficarei muito grato.
 
 
[há +1 ano] - Responder

 

Walter Donizete Faria De Oliveira
Qual a versao utilizada do INDY .
Estou tentando com a que tem no D7, mas nao da .
 
Obrigado.
[há +1 ano] - Responder

 

Feesc
Alguma solução para o problema relatado pelo Walter?

Aguardo.
[há +1 ano] - Responder
 

[autor] Paulo Quicoli
Se não me engano, na época desse artigo foi utilizada a versão 10 do Indy. 
[há +1 ano] - Responder
 

Marivaldo Santos
Paulo, nao consegui encontrar os arquivos para download.

pode enviar pra mim?

admin@onyxsistemas.com

desde ja agradeço, obg
[há +1 ano] - Responder
 

[autor] Paulo Quicoli
Pessoal do site, dá uma mão aí quanto ao download do arquivo :)
[há +1 ano] - Responder
 

Devmedia - Equipe De Moderação
Marivaldo,
os downloads da revista está em http://www.devmedia.com.br/clubedelphi/downloads/ed102/CD102.zip
[há +1 ano] - Responder
 

Maiquel Parisotto
Primeiramente parabeniza-los pelo artigo, muito bom.
Como eu faço para deixar o programa em um servidor e conectar varios suportes nele? Ele funcionaria assim, ou precisa alguma mudança?
Att;
Maiko
[há +1 ano] - Responder

 

Leonardo Bruno
Paulo :

Parabéns pelo artigo.

Eu sou iniciante e nunca tinha trabalhado com indy.
Fiz algumas adaptações e se encaixou perfeitamente com a minha necessidade.

Uma pergunta.

Como eu coloquei o cliente como mdichild dentro da minha aplicacao, eu preciso configurar o envento on close dos forms de mensagem.

Mas quando faço isso, da access violation, suspeito que seja por causa dos forms serem criados em tempo de execução.

Tem alguma ideia de como posso contornar isso ?

Obrigado !!!
[há +1 ano] - Responder

 

[autor] Paulo Quicoli
Olá... puxa, sem ver a estrutura que vc montou fica um pouco complicado... mas dá uma olhada no padrão de projeto Mediator, acho que ele pode te ajudar no que vc precisa...
[há +1 ano] - Responder
 

Leonardo Bruno
Eu nao alterei o codigo do client nem da janela de mensagem.
Apenas coloquei ambos dentro do meu projeto e passei os forms pra MDIchild.

Por ser MDI Child, é necessário configurar o evento onClose do form, pq senao ele só minimiza.

No evento onClose eu coloquei:

FrmNomeDoForm.Release;
FreeAndNil(FrmNomeDoForm);

Mas ao fazê-lo da erro de access violation.

Talvez por existir mais de 1 janela de mensagens pois elas sao criadas em tempo de execucao pelo Cliente, entao ao executar o codigo pra liberar a janela, a aplicacao nao sabe qual janela fechar. Nao seria isso
[há +1 ano] - Responder
 

[autor] Paulo Quicoli
Acho que seu Close não da janelas Child não está certo... o correto não seria utilizar apenas

Action:=caFree ???


Leia isto: http://delphi.about.com/od/beginners/l/aa031103a.htm

Porque do jeito que vc fez, vc está apagando a referência, dentro da referência....
[há +1 ano] - Responder
 

Leonardo Bruno
Olá Paulo.

Obrigado pela resposta.
Eu testei do jeito que você falou e funcionou, consigo fechar a janela.

Mas se o mesmo usuario me chamar denovo, da "invalid pointer operation" e nao cria a nova janela.

Desculpe o incômodo e obrigado mais uma vez !
[há +1 ano] - Responder
 

[autor] Paulo Quicoli
Uma vez dado caFree, a referência se foi... vc precisa instanciar novamente eu acho... naquele link que passei tem links relacionados ao gerenciamento de MDI... dá uma lida lá...

abraço
[há +1 ano] - Responder
 

Leonardo Bruno
Como faço pra apagar a janela da lista de janelas ?

Acho que se eu fizesse isso antes de dar o CAfree resolveria.

[há +1 ano] - Responder
 

Leonardo Bruno
Me parece ser este o problema.
Quando eu dou CAFree sem apagar a janela da lista, quando executa a funcao ListaJanelas ele encontra a janela na lista, mas ela nao existe mais.

Daí o erro ...

Existe algum modo de no envento onClose do ChatF eu apagar a janela da lista que esta criada la no DevChat ?

Mais uma vez muito obrigado !
[há +1 ano] - Responder
 

Leonardo Bruno R. Pereira
Pessoal, sei que o post ja é antigo, mas como nao estou conseguindo resolver o meu problema, nao custa tentar.

Primeiramente gostaria de agradecer e parabenizar o autor.
Dos milhoes de artigos que encontrei no google, poucos funcionaram pra mim, devido a questoes de compatibilidade entre versoes do indy, ou mesmo por versoes antigas do delphi.

Vamos ao problema.

O projeto funciona perfeitamente em localhost, mas ao copiar os clientes para outras maquinas da rede local, mesmo com os firewalls e anti virus das maquinas cliente e do servidor desativados, fica dando erro de socket 10061.

Outra duvida, é a seguinte:

Ja que a ferramenta foi desenvolvida com o intuito de servir de canal de atendimento de suporte entre clientes, como fazer pra roda-la com o servidor nao estando na rede local. Ou seja, onde especifico o ip do servidor, para que os hosts remotos consigam comunicar-se com ele ?

Suponho que na rede local, esteja fazendo broadcast pra saber onde esta o servidor. Estou certo ?

[há +1 ano] - Responder

 

[autor] Paulo Quicoli
o tcpClient está apontando para localhost, e se comunica com o tcpServer através de uma porta especificada.

Se vc colocar o tcpServer em outro computador, vc precisa alterar o tcpClient para apontar para o ip do "servidor" que está rodando o tcpServer. Se vc tentar rodar o Client tentando acessar localhost (que é o padrão), e não tiver o server rodando nele, vai dar o erro que vc mencionou.
[há +1 ano] - Responder
 

Leonardo Bruno R. Pereira
Amigo, muito obrigado pela ajuda !!
E mais uma vez parabéns pelo Post ...

Só uma última pergunta :

Como altero a propriedade Host do IDtcpClient em tempo de execução ?

Nao importa onde tento executar, sempre fala que a propriedade Host não é acessível daqui .
[há +1 ano] - Responder
 

Leonardo Bruno R. Pereira
Amigo, obrigado mais uma vez ...

Eu ja vi o q tava errado ... os nomes do componente estavam me confundindo, eu chamei a classe ao inves do componente q tava no form....

Valeu !!!!!!!
[há +1 ano] - Responder
 

Guaracy Manoel
ola, Paulo sou iniciante, o sistema roda normalmente na rede local, mas quando utilizo via internet, o devchatclient.exe por exemplo da erro 10060 time out, ja coloquei o ip do servidor no tidtcpclien, porta e tudo mais, quando solicito conectar da esse retorno 10060, nao encontrou, ip ou a porta esta vazia, algo do tipo, o cliente esta com windows seven, no servidor xp. desde ja obrigado.
[há +1 mês] - Responder
 

Wesley Yamazack
Olá Guaracy, entramos em contato com o Paulo para que ele possa te auxiliar.

Um abraço
[há +1 mês] - Responder
 

[autor] Paulo Quicoli
Você consegue pingar o endereço/porta do servidor? Será que não é um firewall?

[há +1 mês] - Responder
 

Guaracy Manoel
ola, tudo bem!, em seu fonte cliente e suporte coloquei o ip da maquina que contem o bco, 192.168.0.101, e conectei o server nesta maquina, (xp), e o executavel suporte funciona normalmente, em seguida eu copie seu executavel cliente para um lap_top windows 7, e tentei conectar, nesta hora vem o erro connection time out, acho que tem haver com este meu ip, porque nao e' um ip de internet real, sou iniciante, bem iniciante. conto com sua ajuda. obrigado
[há +1 mês] - Responder
 

Guaracy Manoel
tudo bem!!! ja postei minha duvida, obrigado abraço
[há +1 mês] - Responder
 

Guaracy Manoel
tudo bem!!! ja postei minha duvida, obrigado abraço
[há +1 mês] - Responder
 

[autor] Paulo Quicoli
vc precisa verificar se consegue acessar o ip da sua maquina, pode ser o firewall do windows bloqueando o acesso.
Verifique isso na maquina servidor e na cliente
[há +1 mês] - Responder
 

Guaracy Manoel
ola, obrigado, me esqueci de mencionar o lap top, esta fora da rede local, inclusive em outro predio, como fazer para ele ler o ip da maquina servidor o ip desta maq servidor na internet e' 177.32.80.174, coloco isso no devchatclient tidtcpclient, e da erro de comunicaçao time out, e com o ip que comum 198.162.0.101, a mesma. o servidor que digo e' uma maquina interna aqui no escritorio com windows xp, o laptop, em outro predio seven. funciona normalmente na internet, so nao conecta a esse servidor aqui na empresa.
[há +1 mês] - Responder
 

[autor] Paulo Quicoli
Verifique se vc consegue acessar o IP e porta usando este site:

http://www.ping.eu/port-chk/
[há +1 mês] - Responder
 

Guaracy Manoel
oba, Paulo mais uma vez obrigado, olha so' fiz o seguinte, minha rede aqui e local, meu ip na net, para 4 maquinas e' o mesmo, entao fiz a inversao, coloquei o bco no lap top, compilei o devchatserver com o datamodule apontando para o ip net, do lap top ficando no caminho do server 186.255.x.x:c:\pasta\chatdatabase.fdb como remoto, rapaz quando chamei o suporte ou cliente na minha redezinha local, foi muito bom, conectou exatamente pela internet o bco no meu lap top. parabens pelo trabalho, espero que esse ip seja fixo, se nao terei outra novela pela frente, ai tu me salva mais uma vez. otimo final de terca feira pra ti.
[há +1 mês] - Responder
 

Daniel Evangelista Da Silva
Muito obrigado por este artigo, consegui-o utiliza-lo perfeitamente.
Seu trabalho aqui tem sido de muita ajuda, muito obrigado.
Mas uma pergunta, queria saber no possível, se teria como explicar como funcionaria
para efetuar o envio de arquivos como se fosse do cliente para o suporte ou do suporte
para o cliente?
Fique com Deus
[há +1 mês] - Responder

 

[autor] Paulo Quicoli
Olá, muito obrigado.

No link a seguir vc encontra uma mini implementação....

http://borland.newsgroups.archived.at/public.delphi.internet.winsock/200612/06120710035.html

abraço
[há +1 mês] - Responder
 

Daniel Evangelista Da Silva
Muito obrigado novamente.
Agradeço muito o seu trabalho.
Vou implementa-lo agora mesmo.
Ao terminar até aviso falando se deu certo.
Uma ótima tarde.
[há +1 mês] - Responder
 

Conde Oliveira
amigo mto bom seu post mas enfrento um problema q ninguem consegue resolver, antes de mais nada sei programar mas sou bem leigo em relacao a redes ok?
fazer esse chat funcionar em rede ok, conheco as propriedades e os eventos, imagine a seguinte situacao:
1 - um empresa possui 1 roteador com 3 computadores, em 1 deles esta o "server" e dois os "client" q se conectam pelo IP INTERNO
2 - uma filial dessa empresa possui apenas 1 computador com um IP REAL como ele vai conectar no server? se ele tentar acessar o IP da maquina vai ser algum 192.168.0.X, se pegar o IP REAL vai tentar conectar no roteador, poderia me ajudar em alguma solução??? como faria para resolver isso, se for alguma configuracao no roteador seria possivel a alteracao via Delphi?
*o problema é bem simples mas ninguem consegue resolve-lo poderia me ajudar :(
[há 16 dias] - Responder

 

[autor] Paulo Quicoli
Olá,

eu acho que vc tem que expor e redirecionar as portas no roteador+firewall. Só isso. Tem que ser feito via software do roteador + firewall.
[há 15 dias] - Responder
 

Conde Oliveira
amigo veja se é isso q eu preciso estou perguntando pq n estou tendo mto sucesso..... caso for isso msm eu q devo estar fazendo algo errado aki e dependendo da sua reposta eu fico mais tranquilo http://nilcemar.blogspot.com.br/2012/03/redirecionamento-de-porta-no-windows.html
[há 15 dias] - Responder
 

[autor] Paulo Quicoli
O link que vc me passou, mostra como redirecionar pelo windows... vc pode tentar isso... Mas, se o servidor que tem o IP real, é o que tem o servidor do chat instalado, acho que é só liberar a porta no firewall do roteador e fazer o redirecionamento pelo roteador.
[há 15 dias] - Responder
 

Conde Oliveira
desculpe so mais uma coisa, firewall do roteador, acho q vc quis dizer firewall do windows ne? como disse sou leigo talvez vc disse certo eu é quem entendi errado XD
[há 15 dias] - Responder
 

[autor] Paulo Quicoli
todo roteador tem um firewall embutido (o meu de casa que é "cachorrinho" tem), então vc tem que liberar nos dois (windows e roteador)
[há 15 dias] - Responder
 
Cursos relacionados
Publicidade
[Fechar]

Você precisa estar logado para dar um feedback.

Clique aqui para efetuar o login
[Fechar]


Este post está fechado. Saiba mais sobre a assinatura MVP!
[Fechar] Você precisa estar logado para dar seu feedback.

Clique aqui para efetuar o login

Caso não tenha um cadastro DevMedia, clique aqui para se cadastrar (gratuito)
web-03
DevMedia  |  Anuncie  |  Fale conosco
Hospedagem web por Porta 80 Web Hosting
2013 - Todos os Direitos Reservados a web-03