Cliente / Servidor TCP con Indy I






Programa que desarrolla un ejemplo de un Cliente / Servidor con los componentes Indy utilizando tIdTCPClient y tIdTCPServer.


¿Que lo diferencia de otros parecidos? pues que utiliza un Thread con su respectivo procedimiento Constructor y Execute para evitar los interbloqueos que se producían en otros ejemplos que he visto por la red.

Según mi experiencia la utilización del tIdAntiFreeze en la aplicación no es suficiente.

El componente tIdTCPServer es multithread por definición, y no se necesita procedimientos que manejen los hilos, todo es transparente aunque depende de cómo escribas la aplicación para un cliente. Si usas el evento OnConnect verás que existe un parámetro en tIdContext que te proporciona un TThreadList que incluye la lista de clientes activos.. Se puede usar ese evento para registrar o añadir clientes y en el evento OnDisconnect para eliminarlos o también se puede utilizar un loop que periódicamaente chequee la lista.

El evento onExecute tiene un parámetro llamado Acontext: tIdContext, por lo que si escribes Acontext.Connection tienes un método para enviar datos a un cliente por ejemplo mediante Acontext.Connection.SendCmd, también tienes la propiedad Acontext.Connection.Socket que tiene una subpropiedad para almacenar la Ip del cliente.



Documentación de Indy:

http://www.indyproject.org/downloads/Indy_9_00_14_src.zip 



Libro sobre indy

http://www.atozed.com/Indy/Book/index.EN.aspx



Un tutorial

http://www.devarticles.com/c/a/Delphi-Kylix/Creating-Chat-Application-with-Borland-DelphiIndy-The-Client/





Todos los procedimientos y funciones que a continuación enumero se refieren al lado del cliente.





Cada vez que se conecta un cliente:

procedure TForm1.btnConectaClick(Sender: TObject);
begin
if IdTCPClient1.Connected then
IdTCPClient1.Disconnect
else
begin
IdTCPClient1.Host := lbEdtServidor.Text;
IdTCPClient1.Port := StrToInt(lbEdtPorta.Text);
IdTCPClient1.Connect(5000);
end;
end;










Y en el evento OnConnected:

procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin

IdTCPClient1.WriteLn('nick='+lbEdtNick.Text); //envia el nick del usuario al server

...

TClientThread.Create(false);  //Hace la llamada al thread



end;




El procedimiento del thread es el siguiente:



type



TClientThread = class(TThread)

protected

procedure Execute; override;

public

constructor Create(CreateSuspended: Boolean);

end;






En la implementation se construye de la siguiente forma:



{ TClientThread }



constructor TClientThread.Create(CreateSuspended: Boolean);

begin

inherited Create(CreateSuspended);

Priority := tpIdle;

FreeOnTerminate:= true;

end;



procedure TClientThread.Execute;

begin

inherited;

with Form1 do

begin

if not IdTCPClient1.Connected then

 exit;

repeat

cmd.text:= IdTCPClient1.ReadLn;

if trim(cmd.text) <> '' then

begin

if VerificaComando(cmd.text,'msg=',true) then

 Synchronize(ShowReceiveMsg)

else if VerificaComando(cmd.text,'list_user=',true) then

 Synchronize(ListUsers)

else if VerificaComando(cmd.text,'nick_existente=',true) then

 Synchronize(NickExistente)

else if VerificaComando(cmd.text,'server_error=',true) then

 Synchronize(ShowServerError)

else Synchronize(UnknowCmd);

end;

until not IdTCPClient1.Connected;

end;

{if not Terminated then

 Terminate;}

end;





Para cerrar la conexión:




procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);

begin

IdTCPClient1.Disconnect;

end;






Para enviar un texto al servidor:





procedure TForm1.btnEnviarClick(Sender: TObject);

begin

 IdTCPClient1.WriteLn(FormatChatMessage(lbEdtMsg.Text,lbEdtNick.Text,cmbUsuario.Text,cboxReservado.Checked));

end;




Las siguientes funciones se ejecutan en el TClientThread.Execute.





function VerificaComando(Recebido, Comando: String; Parcial: boolean): Boolean;

begin

recebido:= AnsiLowerCase(trim(recebido));

comando:= AnsiLowerCase(trim(comando));

if not Parcial then

 result:= AnsiSameText(recebido, comando)

else result:= pos(comando, recebido) <> 0;

end;



function ReceiveMsg(Received: String): String;

var

i: integer;

de, para, aux: ShortString;

msg: String;

begin

i:= pos('=',Received);

aux:= copy(Received,i+1,length(Received));



i:= pos('#',aux);

de:= copy(aux,1,i-1);

delete(aux,1,i);



i:= pos('#',aux);

para:= copy(aux,1,i-1);

delete(aux,1,i);

msg:= aux;



i:= pos('#',msg);

if i = 0 then

 result:= de + ' habla con ' + para + ': ' + msg

else

begin

delete(msg,i,length(msg));

result:= de + '  habla reservadamente con ' + para + ': ' + msg

end;

end;



function RemetenteMsg(Received: String): ShortString;

var

i: integer;

begin

i:= pos('=',Received);

received:= copy(Received,i+1,length(Received));



i:= pos('#',received);

result:= copy(received,1,i-1);

end;



function DestinatarioMsg(Received: String): ShortString;

var

i: integer;

begin

i:= pos('=',Received);

Received:= copy(Received,i+1,length(Received));



i:= pos('#',Received);

delete(Received,1,i);



i:= pos('#',Received);

result:= copy(Received,1,i-1);

end;





function FormatChatMessage(Msg, UsuarioOrigem, UsuarioDestino: String; Reservado: Boolean = false): String;

begin

result:= 'msg=' + UsuarioOrigem + '#' + UsuarioDestino + '#' + msg;

if Reservado then

 result:= result + '#reservado';

end;






Próximamente publicaré el código fuente correspondiente al Server (por no hacer más largo este post)  y el programa completo.






 Tienen más información aquí (Idioma holandés)




























2 comentarios:

  1. Hola. Seguí todas tus indicaciones y funciona muy bien, mientras la app está activa. (el cliente es un celular android) Ahora cuando la app pasa a segundo plano y utilizo el celular para otra cosa, después de unos 10 minutos, salta un error en el write. Y si intento abrir de nuevo la conexion me echa del sistema.
    ¿Como puedo mentener la conexión siempre abierta para que no falle?

    ResponderEliminar
  2. Con Android 11 ha cambiado el tema de la actividad de apps en background, por ejemplo, si usas el GPS tienes que crear junto a tu app un servicio que envíe una notificación al usuario para que la app pueda obtener los datos del GPS del dispositivo, en caso contrario la app se queda inactiva en segundo plano.
    Busca en google:
    delphi android background application


    ResponderEliminar

Simulación del movimiento de los electrones en un campo electrico

Espectacular simulación realizada con OpenGL del movimiento de los electrones cuando atraviesan un campo eléctrico. Como muestra la image...