26 septiembre 2008

Enviar mensajes entre aplicaciones con IdTCPClient

Para rematar mas o menos por encima el tema de envío de mensajes entre aplicaciones (aunque se podría hacer también por FTP, IRC, TELNET, etc.) vamos a ver como enviar mensajes utilizando el componente de la clase TIdTCPClient de la paleta de componentes Indy.

A diferencia del procotolo UDP, el protocolo TCP establece una conexión estable y permanente entre el emisor y el receptor asegurando que el mensaje ha llegado a su destino. Si no se puede conectar entonces no puede enviarse el mensaje.

El único inconveniente que tiene este protocolo es que no es tan rápido como el protocolo UDP, aunque si es más seguro para comunicaciones importantes como pueden las conversaciones de un cliente de mensajería instantánea, la transferencia de datos masivos entre aplicaciones (P2P) o el envío de señales críticas entre sistemas distribuidos.

Vamos a ver como crear los dos proyectos para enviar y recibir mensajes.

CREANDO LA APLICACIÓN QUE ENVÍA EL MENSAJE

Creamos un nuevo proyecto y en el formulario principal podemos estos componentes:


En el formulario añadimos dos componentes TEdit para que el usuario escriba la IP y el puerto con el que va a realizar la conexión. También tendrá el componente de la clase TMemo llamado Mensaje donde escribiremos el texto a enviar. Por último añadimos el componente IdTCPClient que llamaremos Cliente y un botón Enviar que va ejecutar este código:

procedure TFEnviar.BEnviarClick(Sender: TObject);
begin
Cliente.Host := IP.Text;
Cliente.Port := StrToIntDef( Puerto.Text, 0 );
Cliente.Connect;
Cliente.Socket.WriteLn( Mensaje.Text );
Cliente.Disconnect;
end;

Lo que hacemos es fijar en el componente Cliente la IP y puerto elegidos por el usuario y después conectamos con el servidor antes de enviar el mensaje. Una vez conectados enviamos el texto utilizando su propiedad Socket y desconectamos.

La comunicación la inicia el programa cliente, que es el encargado de abrir la conexión y es recibida por el programa servidor que tiene ya un puerto abierto en escucha (hemos puesto el 80) esperando a que alguien contacte con él. Una vez establecida la conexión puede comenzar la transferencia de datos.

Si los mensajes se van a enviar muy a menudo, no es necesario que el cliente conecte y desconecte continuamente, sino que podemos dejar la conexión abierta:

procedure TFEnviar.BEnviarClick(Sender: TObject);
begin
Cliente.Host := IP.Text;
Cliente.Port := StrToIntDef( Puerto.Text, 0 );

if not Cliente.Connected then
Cliente.Connect;

Cliente.Socket.WriteLn( Mensaje.Text );
end;

Vamos a ver ahora como implementar el servidor.

CREANDO LA APLICACIÓN QUE RECIBE EL MENSAJE

Hacemos un nuevo proyecto con este formulario:


En este formulario vamos a insertar dos componentes:

- Un componente TMemo llamado Mensaje que va contener el texto recibido.

- Un componente IdTCPServer que llamaremos Servidor.

Al servidor le vamos a poner en su propiedad Port el valor 80. Después lo activamos poniendo su propiedad Active a True.

En el evento OnExecute del componente Servidor se ejecutará este código:

procedure TFRecibir.ServidorExecute(AContext: TIdContext);
var
sMensaje: String;
begin
sMensaje := AContext.Connection.Socket.ReadLn;
Mensaje.Lines.Add( 'De: ' + AContext.Binding.PeerIP );
Mensaje.Lines.Add( 'Mensaje: ' + sMensaje );
end;
Una aclaración: Para que este evento funcione correctamente hay que añadir en nuestro formulario un enlace a la unidad IdContext ya que sino lo hacemos nos dará un error al compilar. De hecho, esta unidad debería haberse insertado automáticamente en el código al añadir el componente IdTCPServer en el formulario. Un tirón de orejas para los programadores de los componentes Indy.

En este procedimiento guardo en la variable sMensaje el texto que envía el cliente a través del socket y luego imprimo en pantalla la IP del emisor y su mensaje.

Ejecutamos ambos programas y pulsamos el botón Enviar:


Al igual que vimos en el envío de mensajes con el protocolo UDP, si el mensaje se va a enviar entre dos aplicaciones que se encuentran en el mismo PC sólo hay que poner como dirección IP la 127.0.0.1.

Con los tres métodos que hemos visto podemos enviar pequeños mensajes en tiempo real entre aplicaciones y separar los programas muy pesados en distintos ejecutables que en envíen la información entre si. Y como no, hacer vuestro propio MSN.

Pruebas realizadas en RAD Studio 2007.

46 comentarios:

Jose Luis dijo...

Hola. Antes de nada darte la enhorabuena por la labor que estás haciendo desde este blog. De verdad que da gusto esperar una semanita para ver qué nos pones de nuevo :)

Mi duda en este artículo es que el componente TidTCPServer me da un error cuando intento colocar el procedimiento Execute. El error que me da es 'undeclared identifier "TidContext"' en la linea

procedure TForm1.ServidorExecute(AContext: TIdContext);


He revisado varias veces el código y no encuentro nada que esté mal. Estoy usando el RAD 2009

Un saludo
Jose Luis

Jose Luis dijo...

Hola de nuevo, he probado el codigo en RAD 2007 y funciona perfectamente :)

Por lo que veo, el 2009 tiene algún tipo de problema con los Servers Indy.

Bueno, si quieres obvia éste mensaje y el anterior porque no aportan nada al ejemplo.

Un saludo y gracias por tu trabajo

Administrador dijo...

Esto ha sido un fallo mío o más bien un fallo de los componentes Indy, ya que cuando se inserta un componente IdTCPServer en un formulario no colocan las unidades que necesitan.

Hay que añadir arriba la unidad IdContext y funcionará correctamente.

Saludos.

Jose Luis dijo...

Pues tienes más razón que un santo :)

Ahora funciona!!!!

Gracias por hacerme la luz :)

Un fuerte saludo

Anónimo dijo...

hola. me tira un error con delphi 2007 en el modulo del cliente en la siguiente linea

Cliente.Socket.WriteLn(Mensaje.Text);


el error es :

[DCC Error] Unit3.pas(59): E2003 Undeclared identifier: 'Socket'.



:P


muy buen blog!!! gracias por tu codigo.


atte:
QuieroProgramarEnDelphi

Anónimo dijo...

hola, soy el mismo de arriba. ya solucione el problema, era un componente idtcpclient y no un udp.

tengo otra duda ahora como ago esto?

"Hay que añadir arriba la unidad IdContext y funcionará correctamente."

de antemano muchas gracias.

atte: QuieroAprenderDeplhi

Administrador dijo...

Pues la verdad es que no se a que puede deberse ese error, ya que el componente IdTCPClient tiene la propiedad Socket.

Asegúrate de que estas poniendo ese componente y no otro (es fácil confundirlo con IdTCPServer).

QuieroProgramarEnDelphi dijo...

eso era. justamente me lo habia confundico con el idudpclient ... jajja gracias por tu respuesta

disculpame, pero tengo itra oregunta. como ago esto?

!"Hay que añadir arriba la unidad IdContext y funcionará correctamente."

como agregar esa unidad????

gracias de nuevo.

Administrador dijo...

Me refería a esto:

uses Windows, Dialogs,..., IdContext;

Lo añades a la claúsula uses y listo.

QuieroProgramarEnDelphi dijo...

muchas gracias por tu tiempo .

maurici0 dijo...

Y para transferir archivos? que no sea ftp???, con el indy?

Administrador dijo...

Para transferir archivos entre aplicaciones se puede abrir un puerto TCP binario y mandarle la información directamente.

Lo que si hay que controlar bien es el crear buffers en memoria para que la transferencia vaya fluida y la posibilidad de continuar otro día en caso de que se corte la conexión.

Eso el protocolo FTP lo hace ya muy bien, con lo cual es tontería reinvenvar la rueda.

Lo que sí sería interesante es crear un sistema de intercambio de archivos P2P simple y básico (y privado).

BuRtOn Z dijo...

Saludos, el proyecto me funciono de maravilla, pero mi pregunta es: Funciona solo con ip estaticas ?, el resto está muy bien, con eso se puede armar un messenger y hacerle la competencia a microsoft, jejeje, Gracias por el aporte, me gustaria ver este mismo proyecto con las variables del UDP.

BuRtOn Z dijo...

Saludos, el proyecto me funciono de maravilla, pero mi pregunta es: Funciona solo con ip estaticas ?, el resto está muy bien, con eso se puede armar un messenger y hacerle la competencia a microsoft, jejeje, Gracias por el aporte, me gustaria ver este mismo proyecto con las variables del UDP.

Administrador dijo...

Da igual si la IP es estática o dinámica. El programa funciona igual.

Otra cosa es si intentas hacer tu propio MSN. Entonces necesitas que el servidor tenga una IP estática o contratar un servidor de redirección gratuito como no-ip.

Saludos.

Pepe dijo...

Hola, muy bueno este pequeño tutorial para iniciarse con los componentes TCP en Indy.

Mi duda es la siguiente, ¿cómo podemos hacer que el servidor sea multihilo?

Salu2.

Administrador dijo...

Eso ya es más complicado. Habría que hacer un servidor multihilo (mediante la clase TThread) que escuchara de un sólo puerto y redireccionara a distintos hilos de ejecución como hace Apache.

No merece la pena programarlo porque para ello ya están los servidores HTTP, Telnet, etc.

Saludos.

Pepe dijo...

Gracias por la respuesta tan rápida amigo, tengo otra duda que preguntarte.

He hecho un servidor TCP con Indy pero sólo puedo comunicarme con él mediante un cliente basado también en Indy, con otros componentes de sockets no me funciona, ¿es esto normal?

Saludos.

Administrador dijo...

Tienes que tener muy claro la información que mandas por el puerto TCP.

Si en algo me revientan los Indy es que es muy difícil comprobar si estás mandando la información bien o no.

Lo que hago es poner un Sniffer (hay muchos gratuitos para Windows) para ver si lo que sale de mi tarjeta de red y de ese puerto es lo correcto.

No hay otra manera más efectiva.

Saludos.

Pepe dijo...

He descubierto cómo hacer que Indy lea correctamente la petición que envíe un cliente que no esté basado en sockets Indy.

En la parte del cliente hay que enviar texto y al final de texto concatenar un salto de línea #13#10 para que Indy pueda leer correctamente la petición en el evento OnExecute con Peticion := AContext.Connection.IOHandler.ReadLn;

Saludos.

Pepe dijo...

Hola, he indigado un poco más acerca de hacer un servidor multihilo con Indy 10 y no es especialmente dificil, no hay que utilizar la clase TThread, el componente TIdTCPServer viene por defecto preparado para utilizar hilos sin usar nada adicional.

El evento OnExecute del TIdTCPServer trata las peticiones de los clientes en un hilo diferente para cada uno.

Hay que tener especial cuidado al procesar la petición pues si la queremos procesar fuera del OnExecute el método que procese la petición tendrá que ser del tipo TIdThreadMethod para que pueda ser pasado como parámetro sobre el método Synchronize.

Yo creo que sí merece bastante la pena programar un servidor multihilo pues puede servir para múltiples aplicaciones clientes, como por ejemplo programar un juego en red.

Saludos.

Administrador dijo...

Si lo investigas a fondo, puede ser muy interesante para crear un programa P2P que comunique a los clientes con el servidor mediante UDP y les envíe la información mediante TCP.

Sería interesante hacer un híbrido entre Emule y Bittorrent ya que sus protocolos se complementan. Pero todo parte de ahí, de servidores TCP y UDP multihilo, donde cada usuario es cliente y servidor a la vez, como en cualquier red distribuida.

Sigue investigando. Merece la pena.

Daniel dijo...

Muchas gracias Administrador, por tan rapida respuesta. Pero como hace el Hamachi? hacer VPN, me parece muy complicado. Lo que estoy tratando de hacer es un programa de juegos que conecte a dos jugadores que sea sencillo y no obligue a redirecciones de nat. He tratado de ayudarme de servicios web, pero tampoco me ha ayudado. Estoy un poco bloqueado. Quizas no tenga solucion lo que trato de hacer.

He tratado de usar el puerto 80 con el tidtcpserver pero me da error : Address and Port are alredy in use. Vamos que da la impresion de que ya se esta usando y no permite usarlo.

Gracias Daniel

Administrador dijo...

Ten en cuenta que los juegos online utilizan un servidor público que tiene ciertos puertos abiertos.

Y tu puedes conectar con quien quieras hacia fuera, pero no hacia dentro (menos algunos firewall avanzados que lo hacen).

Las redes privadas virtuales te dan una IP pública con la que puedes hacer lo que te de la gana: instalar el servidor web Apache, un tracker para BitTorrent o un servidor FTP. Por supuesto las mejores son las pago.

Además gozan de la ventaja de proteger tu IP real de posible ataques.

Creo que lo ideal es alquilar un servidor privado virtual (sobre 80€ al mes) como VMWare, VirtualBox ,etc., que permiten instalar cualquier sistema operativo así como cualquier software servidor (ideal para montar nuestro propio servicio cloud computing).

Creo que ese será el futuro de los servidores y las redes distribuidas para dar servicio a los clientes independientemente de su tipo de conexión.

Saludos.

Anónimo dijo...

Una pregunta me funciona bien pero cuando mando una palabra con la letra "ñ" al momento de recibirla pone "n" y las palabras que tienen acentuacion se lo quita?

Administrador dijo...

Pues acabo de probarlo y a mi me aparecen perfectamente los caracteres ñ así como las tildes en las sílabas.

Anónimo dijo...

Alguna idea del por que a mi no me muestra la ñ o letras con tilde. estoy usando delphi 2009. En cuestiones del codigo no he modificado nada es igual al que presentas.

Administrador dijo...

Pues creo que se debe a que en Delphi 2009 han cambiado el tipo string a unicode.

Mis programas antiguos (incluidos los hechos en Delphi 2007) no van ni de coña.

Tengo que esperar a que salgan los componentes para Delphi 2009. Para mi gusto creo que han hecho cambios demasiado radicales.

Saludos.

Anónimo dijo...

Ok gracias, intentare hacerlo en Delphi 7.

Anónimo dijo...

Hola quisiera saber si es posible hacer que una vez ecibido el mensaje el servidor conteste de manera automatica al cliente. por ejemplo: cuando el cliente envie "Hola" el servidor responda "bienvenido" al cliente. todo basandonos en el tuto obviamente. gracias

Anónimo dijo...

saludos, me gusto el tema y estoy tratando de aplicarlo pero quiero aprender conexion inversa, seria posible que hicieras un ejemplo? o por lo menos explicaras como hacer un boton en el servidor que genere una respuesta del cliente?. trate de hacerlo pero no se como. Gracias

Administrador dijo...

Tienes que utilizar en el servidor el evento OnRead para contestarle al cliente en cuanto de llegue el mensaje.

Lo de la conexión inversa es exactamente lo mismo, la aplicación que hace de servidor solo tiene un componente IdUDPCliente y el servidor tiene IdUDPServer. Todo es cuestión de organizarse.

Saludos.

Anónimo dijo...

Hola, antes que nada gracias por tomarte el tiempo de contestar las preguntas. Con respecto a la conexion inversa segun lo que entiendo entonces no existe la conexion inversa como tal si el caso es que el cliente hace de cliente y el servidor como servidor... o no me quedo claro para nada, si es inversa no debiera conectarse el cliente al servidor y el mismo servidor darle las ordenes al cliente?. disculpa por molestar tanto pero realmente me interesa aprender. gracias

Administrador dijo...

Para que entiendas el tema, te expondré el caso de los troyanos que se programan actualmente.

Cuando un troyano necesita acceder a una víctima, el problema actual se encuentra en que está detrás de un router, por lo que no sabe la IP de la tarjeta de red del PC y seguro que su IP pública tampoco (porque hoy en día casi todas son dinámicas).

Entonces, ¿como se introduce un troyano en el cliente y se controla? El introducirlo es siempre instalando un juego pirata o cualquier otra utilidad. Este troyano dentro del cliente lee de vez en cuando una cuenta de correo o un servidor IRC para saber si recibe instrucciones del atacante.

Cuando el atacande se decide entrar en la víctima, le envía una notificación con una IP pública y un puerto, por lo que la víctima se conecta al atacante y no al revés. A esto se lo denomina conexón inversa. El atacante debe abrir un puerto de su router con las opciones NAT para que la victima pueda acceder.

¿Entiendes como va el tema?

A saber hoy en día lo que hará Microsoft con sus actualizaciones automáticas u otra empresa de antivirus porque pueden hacer con nuestro PC lo que les venga en gana.

Anónimo dijo...

Bueno de nuevo gracias por tomarte el tiempo de responder.
Lo que entendi es que ya no estamos hablando solo de componentes tcp sino tambien de algun irc o mail, que supongo se incluira dentro de la aplicacion, por lo tanto ya no se pareceria en nada al programita de la explicacion que diste. Me gustaria si no es mucha molestia que hicieras un ejemplo bien simple como el de esta pagina pero con coexion inversa. Me serviria de base para poder entender mejor. Gracias

Administrador dijo...

Pero es que el ejemplo ya lo tienes. El que tiene que hacer de servidor hace de cliente (IdTCPClient), y el que hace de servidor hace de cliente (IdTCPServer).

Aunque yo prefiero utilizar ambos a cada lado. La solución ideal es crear una mensajería entre el cliente y el servidor con un servidor público intermedio, implementado por ejemplo en PHP.

Saludos.

Anónimo dijo...

Hola de nuevo, lo estuve intentando mientras esperaba una respuesta tuya, pero el gran problema que tengo es del lado del servidor por ejemplo yo quiero poner un boton en el servidor para que envie un mensaje determinado al cliente pero no encuentro la manera de enviar ese mensaje al cliente, o sea la idea general seria enviar un mensaje desde el servidor sin que el cliente me haya pedido nada se entiende, probe con los componentes tidcmdtcp. pero como dije me es imposible enviar algo al cliente sin que este me lo pida

Administrador dijo...

Lo que quieres es prácticamente imposible, porque si el cliente tiene IP dinámica y además esta detras de un router, no hay manera de llegar hasta él a menos que el cliente establezca primero una conexión contigo (conexión inversa).

Yo tengo también este problema, ya que tengo que hacer un actualizador/monitorizador de aplicaciones sin tener que abrir los routers de los clientes. Sólo se me ocurre hacer una VPN utilizando un programa como Hamachi o bien utilizar un servidor de Internet como puente.

Es un tema complicado, pero creo que hay que fijarse en como trabajan los antivirus para hallar la solución: un servidor dedicado.

Saludos.

Anónimo dijo...

Hola, como estas?.
Bueno perdon por no ser tan claron en msi dudas pero me es dificil explicar cual es mi objetivo. Yo se que en una conexion inversa el cliente establece una conexion con el servidor, en un principio, lo que yo busco es que despues de esa conexion el control quede a manos del servidor, o sea una vez por ejemplo: el cliente se conecta al servidor(por conexion inversa), entonces el servidor desea enviarle un comando a el cliente, mi duda es como hacer un boton para que el servidor le envie ese comando al cliente, en cualquier momento despues de la que se haya establecido la conexion. Epero haber sido lo suficientemente claro. Te cuento he intentado de varias formas mientras me vas ayudando(Muchas muchas muchas gracias) pero no logro hacerlo. gracias de nuevo :)

Anónimo dijo...

Hola, de nuevo. Perdon por complicarte la vida. Mira yo se que el cliente tiene que establecer una conexion con el servidor antes de recibir nada pero mi gran problema es hacer que el servidor le envie, por ejemplo una orden para ejecutar determinado comando por medio de un boton en el servidor. esto sin que el cliente me haya solicitado dicha orden, espero ser mas claro con mis dudas, estuve intentando de varias formas pero no logro hacerlo. Gracias

Administrador dijo...

Para hacer lo que tu quieres tienes que utilizar la propiedade ABinding que hay dentro del evento OnRead que nos dará esta información:

ABinding.PeerIP -> La IP del que contactó contigo por primera vez (el servidor).

ABinding.PeerPort -> Puerto por que el recibes los mensajes del cliente.

Como lo que tu quieres hacer es enviarle un mensaje, si lo quieres hacer instantáneamente tienes que utilizar la propiedad ABinding, en caso contrario esta otra:

ServidorTCP.Bindings.Items[i].

Ahí está la colección de conexiones que los clientes han contactado contigo y cuya conexión todavía está abierta, es decir, los sockets que tienes abiertos.

Estos Bindings también tienen métodos para envíar mensajes al cliente (Send, SenTo, etc.). Esto solo funciona en conexiones TCP ya que las conexiones UDP no dejan sockets abiertos.

Saludos y suerte.

Anónimo dijo...

quiero hacer el servidor y el cliente en la misma ventana es posible poner los mismos codigos?, y que no generen problema?

Administrador dijo...

En principio no debería haber ningún problema aunque sea el mismo programa que haga de servidor y cliente.

Saludos.

Anónimo dijo...

Saludos. Yo lo habia pénsado de esa manera pero el dilema es que en indy no hay evento onread. cual seria el equivalente? uso delphi 2009

juan ignacio dijo...

hola

Omar ordoñez dijo...

cuando lo que se recibe viene de un gps vehicular y la cadena que envia es en hexadecimal como sepude hacer para recibir dicho mensaje en hex
gracias

Publicidad