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)



Relacionados:

Cliente / Servidor TCP con Indy II


Cliente / Servidor UDP

Código fuente de un proxy con Synapse

Obtener el NS, MX, SOA de un dominio con Indy







1 comentario:

  1. Is always good to assign credit to the original author ;)

    ResponderEliminar