Client/Server Com TIDTCP Server

04/11/2005

Pessoal,

Estou desenvolvendo um programa client/server usando a biblioteca Indy 10 para comunicação entre si em diferentes máquinas da rede.

Criei uma aplicação servidora e acrescentei um TIDTCPServer e TIDServerIOHandlerStack.

Na aplicação cliente coloquei um TIDTCPClient e um TIDIOHandlerStack.

Consigo conectar com o client normalmente no servidor, fazer uma verificação de login e tudo.

Estou com dois problemas.

O primeiro é que algumas vezes o Server precisa enviar alguma informação para o client através desta conexão, mas com esses componentes do Indy, só consigo enviar informações ao client quando o mesmo solicitar alguma coisa, usando a variável AContext passada como parametro no OnExecute do IDTCPServer. E como a conexão é bloqueante, quando o Client solicita alguma informação do Server, ele fica aguardando essa resposta. Mesmo se eu conseguisse enviar alguma coisa direta do Server sem a solicitação do Client, o mesmo não estaria aguardando por esta informação e o programa não funcionaria como o esperado.

Antes de migrar para Indy, eu usava Sockets direto (TClientSocket e TServerSocket) e conseguia fazer essa comunicação Server->client normalmente.

Alguém tem alguam dica?
Bom, eu pensei em uma maneira mais simples de resolver este problema. Estou tentando fazer o seguinte:

Acrescentei um TIDTCPServer no meu Client e um TIDTCPClient no Server (fiz o inverso) e quando o cliente faz a conexão com o server, envia uma porta e o IP para que o server por sua vez faça uma conexão no programa cliente usando o seu IDTcpServer.
Só que naum estou gostando muito dessa solução. Pra mim está parecendo meio que gambiarra... até porque se eu tiver 1 client conectado tudo bem, mas e quando existirem 50, 100 clientes conectados? Vai pesar muito por causa dessas duas conexões efetuadas??

Alguma sugestão??

Se essa realmente for uma boa alternativa, caio em outro problema, como posso fazer um teste para verificar se essa porta já está sendo usada? Estou usando portas altas, acima de 15.000 mas pode acontecer de já estar sendo usada.

Muito obrigado pela atenção.


Prgdelphi

Respostas

04/11/2005

Massuda

Eu uso Indy 9, não tive contato ainda com Indy 10.
...algumas vezes o Server precisa enviar alguma informação para o client através desta conexão, mas com esses componentes do Indy, só consigo enviar informações ao client quando o mesmo solicitar alguma coisa.... Acrescentei um TIDTCPServer no meu Client e um TIDTCPClient no Server (fiz o inverso) ...
A solução que você adotou é uma das duas possíveis (e a mais simples).

A outra solução seria, no programa cliente, usar uma thread para fazer leitura dos dados que chegam do servidor; essa thread tem que ter uma máquina de estados finito (ou qualquer estrutura de controle que você ache conveniente) de modo que você saiba distinguir uma resposta do servidor a um comando enviado pelo cliente de uma mensagem/comando do serivdor para o cliente. Geralmente isso é fácil de implementar para programas simples (chat por exemplo), mas pode ficar muito complexo dependendo do seu protocolo de comunicação. Ficaria algo assim (beeem simplificado)...
type
  TReadingThread = class(TIdThread) 
  protected 
    FConn: TIdTCPConnection; 
    procedure Run; override; 
  public 
    constructor Create(AConn: TIdTCPConnection); reitroduce; 
  end; 
....
constructor TReadingThread.Create(AConn: TIdTCPConnection); 
begin 
  FConn := AConn; 
  inherited Create(False); 
end; 

procedure TReadingThread.Run; 
var 
  Command, Data: String; 
begin 
  Command := FConn.ReadLn; 
  if Command = ´MESSAGE´ then 
    Data := FConn.ReadLn; 
... 
end; 

...

type
  TSeuForm = class...
    IdTCPClient1: TIdTCPClient;
  private
    Reader: TReadingThread; 
  end;

procedure TSeuForm.IdTCPClient1Connected(Sender: TObject); 
begin 
  Reader := TReadingThread.Create(IdTCPClient1); 
end; 

procedure TFrmClient.IdTCPClient1Disconnected(Sender: TObject); 
begin 
  if Reader <> nil then begin 
    Reader.TerminateAndWaitFor; 
    FreeAndNil(Reader); 
  end; 
end; 
...como usa uma thread para ler os dados que chegam do servidor, possivelmente precisa implementar algum mecanismo de sincronização para poder passar os dados para outra thread.

Os gurus em Indy recomendam a segunda alternativa, mas pessoalmente prefiro a primeira (a que você adotou) por que ela é mais fácil de manter.

No caso do Indy 10, li que existe um componente chamado TIdCmdTCPClient, que tem uma propriedade CommandHandlers parecido com a que tem no TIdTCPServer.

...como posso fazer um teste para verificar se essa porta já está sendo usada? Estou usando portas altas, acima de 15.000 mas pode acontecer de já estar sendo usada.
Imagino que você está se referindo ao TIdTCPServer que você pos no programa cliente. Quando você tentar ativar o servidor e a porta estiver em uso, ele vai gerar uma exceção; trate essa exceção e tente em uma porta diferente (por exemplo, 15.001). No seu protocolo de comunicação, inclua a possibilidade do programa cliente informar ao programa servidor qual porta ele está usando.


Responder Citar

04/11/2005

Prgdelphi

É isso aí mesmo Massuda, eu já tinha lido alguma coisa a respeito desta thread no cliente para receber as informações, mas acho que é uma solução não muito prática exatamente por utilizar o esquema de máquina de estados. Quando usava conexão por sockets puro utilizava uma máquina de estados também, tanto no server quanto no client. Mas quando a quantidade de clients conectados aumentava muito, o server ´se perdia´.

O TIdCmdServer e TIDCmdClient existem sim no Indy 10, mas também tive alguns problemas com os dois e por isso optei pelo TIDTCPServer e client, usando eles eu primeiro envio o tamanho do Texto ou stream que vou enviar e em seguida envio o mesmo. E o outro lado fica aguardando o término. Até hoje não tive mais problema usando esses componentes, o único inconveniente é este, de naum poder enviar dados do server sem a solicitação do client.

O problema da porta vai ocorrer dos dois lados, pois se o Server vai instanciar um novo componente TIDTCPClient para cada cliente conectado, ele também deverá verificar se a porta não está em uso por outro client.
Ou então uma outra alternativa que eu poderia usar seria instanciar um só TIDTCPClient e usar uma porta única e quando o server precisar enviar alguma informação pro client, ele conecta, envia e desconecta.

O que você acha?

Obrigado.


Responder Citar

04/11/2005

Massuda

O problema da porta vai ocorrer dos dois lados, pois se o Server vai instanciar um novo componente TIDTCPClient para cada cliente conectado, ele também deverá verificar se a porta não está em uso por outro client.
Acho que não tem esse problema no programa servidor, já que ele vai usar o IP do cliente mais a porta que o cliente informar para configurar o TIdTCPClient.

Se dois clientes em IPs diferentes usarem a mesma porta, não há problema, pois os IPs são diferentes. Se estiverem no mesmo IP, os clientes não poderão usar a mesma porta, já que eles não seriam capazes de ativar seus TIdTCPServer na mesma porta (um deles vai falhar, mas você deveria tratar isso como comentei no final do meu post anterior).

O que eu geralmente faço é ativar sempre o TIdTCPServer do programa cliente quando inicio a comunicação com o servidor, assim posso passar ao servidor qual porta o TIdTCPServer está utilizando de fato.


Responder Citar