idTcpClient pode ficar escutando os comandos do idTcpServer?

Delphi

20/03/2006

Ai pessoal, pelo que entendi os componentes indy funcionam em um meio de pergunta e resposta, certo?

O cliente faz uma pergunta o servidor responde, o servidor faz uma pergunta o cliente responde e assim por diante. Se o cliente fizer mais de uma pergunta por vez o servidor enviará uma reposta de cada vez até por que será feita uma resposta por vez:
IdTcpClient.WriteLn(‘#Comando’);
ShowMessage(idTcpClient.ReadLn());


Minha dúvida é a seguinte: Se eu quiser enviar um comando do cliente para o servidor basta colocar um botão e fazer:
idClient.WriteLn(‘HORA’); //um exemplo para pedir a hora do servidor
MsgResposta := idClient.ReadLn();//Aqui já pego a resposta do servidor, certo?

:arrow: :arrow: No servidor coloco no evento OnTcpServerExecute;
Var
Msg : String;
Begin
Msg := UpperCase(idTcpServer.ReanLn());
If Msg = ‘#HORA’ then // se o cliente estiver pedindo a hora envio para ele uma resposta...
 idTcpServer.WriteLn(Time); 
…

Resumindo, sabemos que para ler as solicitações do cliente basta colocar uma instrução neste evento no servidor, certo? Mais e no cliente, como faço para aguardar os comandos do servidor. Tentei colocar um timer para ficar assim: :idea: :idea: :idea:
var
 msg : String;
begin
msg := ´´;
if IdTCPClient.Connected then
 begin
  msg := IdTCPClient.ReadLn();
  if msg <>´´ then
   begin
    ListBox1.Items.Add(msg);
   end
  else
   begin
    Exit;
   end;
 end;
end;
só que a minha aplicação trava quando não há nenhuma mensagem vinda do servidor!... :!: :!: :!:

Minha pergunta é tem como o cliente ficar escutando os comandos do servidor, sem ser nas seqüências abaixo?
IdTcpClient.WriteLn(‘#Comando’);
ShowMessage(idTcpClient.ReadLn());

:?: :?: :?:


Paullsoftware

Paullsoftware

Curtidas 0

Respostas

Massuda

Massuda

20/03/2006

Talvez [url=http://forum.clubedelphi.net/viewtopic.php?t=70214]este tópico[/url] seja útil para você. Tem dois modos de resolver isso, uma é usando uma thread para o cliente ler o que chega do servidor ou você usar um TIdTCPServer também no cliente. Na versão 10 do Indy tem novos recursos para suportar isso, mas eu não conheço como usar (não uso a versão 10).


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

Massuda, tentei implementar seu o código do Tópico que vc informou, mais não compila, dar um erro nessa linha:
type
  TReadingThread = class(TThread)
    protected
    FConn: TIdTCPConnection;
    procedure Run; override;//aqui da o erro!
    public
    constructor Create(AConn: TIdTCPConnection); reintroduce;
    end;
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    Button1: TButton;
    IdTCPClient: TIdTCPClient;
    SpeedButton1: TSpeedButton;
    Edit2: TEdit;
    SpinEdit1: TSpinEdit;
...


só resaultando, copiei e colei o código para testar! :roll:


GOSTEI 0
Massuda

Massuda

20/03/2006

:oops: Onde está [i:80eabd98ea]procedure Run[/i:80eabd98ea], é [b:80eabd98ea]procedure Execute[/b:80eabd98ea].


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

:oops: Onde está [i:09d8228d08]procedure Run[/i:09d8228d08], é [b:09d8228d08]procedure Execute[/b:09d8228d08].

Reader.TerminateAndWaitFor;

aqui também é só terminate?? :?: :?:


GOSTEI 0
Massuda

Massuda

20/03/2006

Olhei novamente o código que está no outro tópico e percebi que a classe TReadingThread é derivada de TIdThread e não de TThread, como você fez; o código original deve funcionar se você mudar isso.

Se preferir continuar a derivar de TThread, o TIdThread.TerminateAndWaitFor corresponde a chamar TThread.Terminate seguido de TThread.WaitFor.


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

funcinou, mais eu coloquei um timer para ficar testando a conexão assim:
if idTcpClient.Connected then
Reader := TReadingThread.Create(IdTCPClient);
setei o interval para 500, na procedure [b:e330f01584]Execute[/b:e330f01584] coloquei para tratar os comandos que vem do servidor e se não for reconhecido to dando um [b:e330f01584]Exit[/b:e330f01584] será que to fazendo certo :?: :?: :?:


GOSTEI 0
Massuda

Massuda

20/03/2006

Não entendi porque você está usando um timer. Não precisa. A thread, depois de ser criada, vai executar sempre que chegar algo do servidor e irá terminar quando houver desconexão.


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

Não entendi porque você está usando um timer. Não precisa. A thread, depois de ser criada, vai executar sempre que chegar algo do servidor e irá terminar quando houver desconexão.

então isso é estranho não está funcionando assim...
vou fazer mais alguns testes e post o resultado! :wink:


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

realmente massuda a Trhade é criada, mais não esta funcionando, alias, só funciona no momento da conexão da estação com o servidor...
quando conecto a estação vem na hora a resposta do cliente:
idTcpServer.WriteLn(´Conectado com Sucesso!´);

mais depois disso se enviar algum comando do servidor não é executado, comente quando o cliente faz uma nova solicitação é mostrar a mensagem que o servidor havia enviado antes.. já quando eu coloco o timer, menseionado anteriormente funciona! o que pode ser?


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

depois de fazer alguns testes, cheguei a esse código:
procedure TReadingThread.Execute;
var
  Command: String;
begin
 With fMain do
  begin
  if not IdTCPClient.Connected then Exit;
  repeat
    Command := UpperCase(FConn.ReadLn);
    if Command = ´#NET´ then FConn.WriteLn(´Desconectado do Servidor´)//ShowMessage(´Desconectado do Servidor´)
    else if Command = ´BLOCK´ then FConn.WriteLn(´Executado com sucesso!´)//ShowMessage(´Terminal Bloqueado´)
    else if Command = ´SHUTDOWN´ then FConn.WriteLn(´Executado com sucesso!´)//ShowMessage(´Desligar Terminal´)
    else if Command = ´LIBERA´ then FConn.WriteLn(´Executado com sucesso!´)//ShowMessage(´Terminal Liberado´)
    else if Command = ´REBOOT´ then FConn.WriteLn(´Executado com sucesso!´)//ShowMessage(´Reiniciar Terminal´)
    else if Command = ´TELA´ then FConn.WriteLn(´Executado com sucesso!´)//ShowMessage(´Enviar Tela do Terminal´)
    else if Command = ´#STOP´ then FConn.WriteLn(´Executado com sucesso!´);//ShowMessage(´Parar Tempo´);
    ListBox1.Items.Add(Command);
    Command := ´´;
  until not IdTCPClient.Connected;
  end;
end;

a principio esta funcionando corretamente, mais o estranho é que quando estava usando as [b:84b1f1c973]ShowMessages();[/b:84b1f1c973] não estava funcionando, sabe informar pq? :?: :?: :?:


GOSTEI 0
Massuda

Massuda

20/03/2006

...quando estava usando as [b:274e51508e]ShowMessages();[/b:274e51508e] não estava funcionando, sabe informar pq?
Porque é uma thread! Todo programa Delphi tem uma thread principal que cuida da interface com o usuário (vulgarmente, a ´tela´); qualquer outra thread que você cria e que precisa acessar a interface com o usuário precisa fazer isso de forma sincronizada (duas threads não podem usar a tela simultaneamente); veja na ajuda do Delphi o método TThread.Synchronize.


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

certo, Massuda...
valeu pelos toques, agora acho que vai dar pra continuar o desenvolvimento do projeto! :wink: obrigado mais uma vez...
problema solucionado! :twisted: :twisted:


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

Op´s surgiu outras dúvidas...

como desconectar ou enviar mensagem para todos os usuários conectados ao servidor???

não achei nos exemplos que olhei sobre o indy...


GOSTEI 0
Massuda

Massuda

20/03/2006

Um exemplo simples seria...
var 
  List: TList; 
  Peer: TIdPeerThread; 
... 
  List := IdTCPServer1.Threads.LockList; //isso bloqueia o servidor! 
  try 
    for I := 0 to List.Count - 1 do begin 
      // List[I] é uma das threads do servidor 
      Peer := TIdPeerThread(List[I]); 
      Peer.Connection.WriteLn(´Oi´); 
    end; 
  finally 
    IdTCPServer1.Threads.UnlockList; 
  end;
...note que entre o LockList e o UnlockList o servidor ficará bloqueado se alguém tentar conectar no servidor; isso acontece porque o servidor precisa incluir um no TIdPeerThread na lista Threads, mas esse código bloqueia a lista temporariamente; moral da estória: seja lá o que você for fazer com cada Peer, procure evitar executar cosias demoradas.


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

seja lá o que você for fazer com cada Peer, procure evitar executar cosias demoradas.
eu to pensando em algo como:
1 - Desconectar todos os Clientes;
2 - Desligar Todos os Clientes;
3 - Reiniciar Todos os Clientes;
4 - Liberar Todos os Clientes
algo desse tipo, tem problemas???


GOSTEI 0
Massuda

Massuda

20/03/2006

Aparentemente em todos os casos o cliente vai ter que desconectar do servidor. Pelo que entendi, não teria problemas, pois você vai apenas mandar um comando e acredito que não há necessidade de esperar o cliente executar o comando.


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

Aparentemente em todos os casos o cliente vai ter que desconectar do servidor. Pelo que entendi, não teria problemas, pois você vai apenas mandar um comando e acredito que não há necessidade de esperar o cliente executar o comando.
a ta, agora entendi, vc é que sempre que há o envio por parte de um deles client/servidor na sequencia eles ficam aguardando uma resposta do outro né isso?
eu imaginei isso também, por exemplo eu pensei em pedir dos clientes com o comando [b:1c03103f21]#HORA[/b:1c03103f21] por exemplo, mais ai pensei: ele vai pedir a hora de todos e depois que o último receber o pedido é que ele vai processar a do primeiro, estou certo?


GOSTEI 0
Massuda

Massuda

20/03/2006

Talvez eu não tenha entendido, mas eu implementaria, por exemplo, o comando DESLIGAR assim....
// código no servidor
var 
  List: TList; 
  Peer: TIdPeerThread; 
... 
  List := IdTCPServer1.Threads.LockList; 
  try 
    for I := 0 to List.Count - 1 do begin 
      // List[I] é uma das threads do servidor 
      Peer := TIdPeerThread(List[I]); 
      Peer.Connection.WriteLn(´DESLIGAR´); 
      if Peer.Connection.ReadLn(10, 100) <> ´OK´ then begin
        // loga/avisa que não respondeu
      end;
    end; 
  finally 
    IdTCPServer1.Threads.UnlockList; 
  end;

// código no cliente
...
  Comando := IdTCPClient1.ReadLn;
  if Comando = ´DESLIGAR´ then begin
    IdTCPClient1.WriteLn(´OK´);
    //prepara para desligar
  end
...
...isso deve funcionar sem problemas no seu caso (lan house né?), onde o número de clientes ligados ao servidor é fixo.


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

Massuda, eu crei uma procedure no servidor para enviar o comando par ao cliente assim:
procedure TfMain.SendAll(wTipo:String);
var
 List : TList;
 Peer : TIdPeerThread;
 I    : Integer;
begin
 List := TCPServer.Threads.LockList;
 try
   for I := 0 to List.Count -1 do
    begin
     Peer := TIdPeerThread(List[i]);
     if wTipo = UpperCase(´:1´) then
      begin
       Peer.Connection.WriteLn(´TELA´)
      end
     else
     if wTipo = UpperCase(´:2´) then
      begin
       Peer.Connection.WriteLn(´STOP´)
      end
     else
     if wTipo = UpperCase(´:3´) then
      begin
       Peer.Connection.WriteLn(´BLOCK´)
      end
     else
     if wTipo = UpperCase(´:4´) then
      begin
       Peer.Connection.WriteLn(´LIBERA´)
      end
     else
     if wTipo = UpperCase(´:5´) then
      begin
       Peer.Connection.WriteLn(´#REBOOT´)
      end
     else
     if wTipo = UpperCase(´:6´) then
      begin
       Peer.Connection.WriteLn(´MSG´)
      end
     else
    end;
 finally
   TCPServer.Threads.UnlockList;
 end;
end;


e no client tenho uma Trhead assim:
procedure TReadingThread.Execute;
var
Command,cmd : String;
begin
With fMain do
begin
repeat
Command := UpperCase(FConn.ReadLn);
cmd := Command;
if Command = ´NET´ then
begin
FConn.WriteLn(IdTCPClient.Socket.LocalName+ ´ Desconectado do Servidor´);
lbStatus.Items.Insert(0,Command);
end;
if Command = ´BLOCK´ then
begin
FConn.WriteLn(IdTCPClient.Socket.LocalName+ ´ Bloqueado com sucesso!´);
lbStatus.Items.Insert(0,Command);
end;
if Command = ´#SHUTDOWN´ then
begin
FConn.WriteLn(IdTCPClient.Socket.LocalName+ ´ Desligado com sucesso!´);
lbStatus.Items.Insert(0,Command);
end;
if Command = ´LIBERA´ then
begin
FConn.WriteLn(IdTCPClient.Socket.LocalName+ ´ Liberado com sucesso!´);
lbStatus.Items.Insert(0,Command);
end;
if Command = ´REBOOT´ then
begin
FConn.WriteLn(IdTCPClient.Socket.LocalName+ ´ Reiniciado com sucesso!´);
lbStatus.Items.Insert(0,Command);
end;
if Command = ´TELA´ then
begin
FConn.WriteLn(´Tela copiado da Estação: ´ + IdTCPClient.Socket.LocalName);
lbStatus.Items.Insert(0,Command);
end;
if Command = ´STOP´ then
begin
FConn.WriteLn(IdTCPClient.Socket.LocalName+ ´ Tempo parado com sucesso!´);
lbStatus.Items.Insert(0,Command);
end;
cmd := UpperCase(Trim(Copy(cmd, 1, Pos(´:´, cmd)-1)));
if cmd = ´!´ then
begin
lbStatus.Items.Insert(0,´Liberado!´);
Button2.Enabled := True;
end
else
if cmd = ´?´ then
begin
lbStatus.Items.Insert(0,Command);
Button2.Enabled := False;
end;
until not IdTCPClient.Connected;
end;
end;
lembra? Ela ainda está incompleta, mais estou desenvolvendo primeiro o server, e fiz o código acima somente para testar, ainda não tá 100¬, mais acho que o caminho é por ai...
Eu tava olhando um dia desses o [b:11672851a1]Projeto Lan-House[/b:11672851a1], mais percebi que o pessoal abandonou ou estão se comunicando por outro lugar, pois, as mensagens que estão lá são muito antigas...


GOSTEI 0
Massuda

Massuda

20/03/2006

...eu crei uma procedure no servidor para enviar o comando par ao cliente assim...mais acho que o caminho é por ai...
É isso mesmo.


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

[quote:b9ee474c3d=´paullsoftware´]...eu crei uma procedure no servidor para enviar o comando par ao cliente assim...mais acho que o caminho é por ai...
É isso mesmo.[/quote:b9ee474c3d]

Obrigado mais uma vez grande amigo Massuda! qq coisa resgato esse tópico! :roll:


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

Grande Massuda :wink:
resolvi resgatar esse tópico, para ficar tudo em um único local, estou com outra dúvida e não sei se é possível fazer essa fasanha...

como faço para pegar o nome da estação através do servidor, digo isso porque estou fazendo assim:
wEstacao := IdTCPClient.Socket.LocalName +´ - ´+IdTCPClient.Socket.Binding.IP;

quando o cliente manda algo para o server em envio junto o nome dele, mais gostaria de saber se tem como fazer isso através do servidor mesmo..
como os Sockets fazem... RemotHost e RemotAddress, é possível fazer com indy :?: :?:


GOSTEI 0
Massuda

Massuda

20/03/2006

Normalmente eu pego esse tipo de informação no evento OnConnect do TIdTCPServer usando algo do tipo...
procedure TExemplo.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
  // AThread.Connection.Socket.Binding.PeerIP = IP do cliente
  // AThread.Connection.Socket.Binding.PeerPort = porta do cliente
end;
...mas não sei dizer como pegar, via Indy, o nome do cliente (normalmente eu uso apenas o IP e a porta).


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

Normalmente eu pego esse tipo de informação no evento OnConnect do TIdTCPServer usando algo do tipo...
procedure TExemplo.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
  // AThread.Connection.Socket.Binding.PeerIP = IP do cliente
  // AThread.Connection.Socket.Binding.PeerPort = porta do cliente
end;
...mas não sei dizer como pegar, via Indy, o nome do cliente (normalmente eu uso apenas o IP e a porta).


certo, hoje eu to fazendo como disse acima, na hora que o cliente conecta envio a seguinte informação:
var
wEstacao:String;
begin
...
wEstacao := IdTCPClient.Socket.LocalName +´ - ´+IdTCPClient.Socket.Binding.IP;
SendCmd(´+´,wEstacao);//onde SendCmd é um procedure que criei para enviar mensagens para o servidor com um determinado identificador seria o mesmo que: [b:def03de3b4][i:def03de3b4]IdTCPClient.WriteLn(´+;´ + wEstacao);[/i:def03de3b4][/b:def03de3b4]. Então, eu envio essas informações e o server trata elas assim:
procedure TfMain.TCPServerConnect(AThread: TIdPeerThread);
var
  Client  : TSimpleClient;
  Cmd,Msg : String;
begin
  Msg := AThread.Connection.ReadLn();
  Cmd := Trim(Copy(Msg, 0, Pos(´:´, Msg) -1));
  Msg := Trim(Copy(Msg, Pos(´:´, Msg) +1, Length(Msg)));
  if Cmd = ´+´ then
   begin
    AThread.Connection.WriteLn(´;: NOME DA ESTAÇÃO NA REDE: ´+Msg);
    AThread.Connection.WriteLn(´#BLOCK´);
    Client := TSimpleClient.Create;
    Client.Name := Trim(Copy(Msg, 0, Pos(´-´, Msg) -1));
    Client.DNS  := Trim(Copy(Msg, Pos(´-´, Msg) +1, Length(Msg))); ;
    Client.ListLink := lbClients.Items.Count;
    Client.Thread := AThread;
    lbClients.Items.Add(Client.Name+ ´ - ´+Client.DNS);
    AThread.Data := Client;
    Clients.Add(Client);
    lbStatus.Items.Insert(0,UpperCase(Client.Name)+ ´ Conectou agora: ´ + FormatDateTime(´DD/MM/YYYY HH:MM:SS´,Now) );
   end;

só que pensei que haveria uma fora de ´pegar´ o nome/ip do cliente e não ´ler´ como fasso. mais valeu...
estou pensando em fazer tudo no server (banco/programação/comandos/contagem do tempo, etc...) tudo no server, nos clientes pensei em mandar somente o exe... seria vantagioso? :roll:


GOSTEI 0
Massuda

Massuda

20/03/2006

só que pensei que haveria uma fora de ´pegar´ o nome/ip do cliente e não ´ler´ como fasso. mais valeu...
Usando o exemplo do meu último post, o servidor pega o IP e porta do cliente (nota: o cliente não enviou essas informações) automaticamente, o que fica faltando é descobrir o nome do cliente pelo IP, mas isso, se não me engano, tem alguns exemplo aqui no fórum.


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

[quote:f9205494dc=´paullsoftware´]só que pensei que haveria uma fora de ´pegar´ o nome/ip do cliente e não ´ler´ como fasso. mais valeu...
Usando o exemplo do meu último post, o servidor pega o IP e porta do cliente (nota: o cliente não enviou essas informações) automaticamente, o que fica faltando é descobrir o nome do cliente pelo IP, mas isso, se não me engano, tem alguns exemplo aqui no fórum.[/quote:f9205494dc]
en entendi, até fiz um teste aqui e funcionou... vlw
[quote:f9205494dc=´paullsoftware´]o que fica faltando é descobrir o nome do cliente pelo IP, mas isso, se não me engano, tem alguns exemplo aqui no fórum.
[/quote:f9205494dc]
quanto a isso vou dar mais uma vasculhada pelo fórum :wink:


GOSTEI 0
Paullsoftware

Paullsoftware

20/03/2006

op´s, ao invés de criar um novo Tópico resolvi resgatar esse para ficar tudo centralizado em um unico lugar...
toda vez que um cliente conecta ao sevidor eu jogo ele em uma lista (Listbox) e tb armazeno suas informações em [b:006f65b589]TThreadList[/b:006f65b589] até ai tudo bem, quando eu seleciono um cliente no listbox ele mostra as informações sobre ele como IP, tempo de acesso e outras informações...
O problema é que o meu ListBox está preparado para ser organizado em ordem alfabética e quando e quando o último cliente conecta ao servidor e esse fica sendo o primeiro da lista e dou um clique sobre ele aparece as informações sobre o primeiro cliente a ser conectado.
a pesquisa é feita assim hoje:
SelClient := pclient(Clients.LockList.Items[lbClients.ItemIndex]);

como faço para fazer essa pesquisa pelo no do item no listbox e não o índice?


GOSTEI 0
POSTAR