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.
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.
ResponderEliminar¿Como puedo mentener la conexión siempre abierta para que no falle?
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.
ResponderEliminarBusca en google:
delphi android background application