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.
...