Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   Mensajes Broadcast vía UDP entre varias instancias corriendo en una misma máquina (https://www.clubdelphi.com/foros/showthread.php?t=81968)

gluglu 09-01-2013 18:39:18

Mensajes Broadcast vía UDP entre varias instancias corriendo en una misma máquina
 
Cuanto tiempo sin aparecer por aquí !!! :o

Saludos a todos en primer lugar.

Necesito de nuevo una ayudita.

Mi aplicación funcionaba perfectamente en red local con varios ordenadores, basándome en comunicaciones con TIdUPDServer y TIdUPDClient para enviar mensajes e información de un puesto a otro. Necesito que sean comunicaciones UDP porque no necesito contestación obligatoria sino que lanzo (Broadcast) mensajes y los demás si lo escuchan, actualizan información que el puesto que envía el broadcast está informando que ha cambiado.

Todo eso funcionaba perfectamente hasta que a algunos clientes se les está ocurriendo (para bien) montar la aplicación en servidores virtuales y accediendo con Escritorio Remoto.

En este caso, tengo múltiples instancias de mi aplicación corriendo a la vez en la misma máquina, y no puedo abrir el puerto UDP que haya asignado en dos instancias diferentes DENTRO DE LA MISMA MAQUINA. Y ahí es donde empiezan mis problemas de comunicación entre aplicaciones ....

Cómo se podría resolver elegantemente, teniendo en cuenta el hecho de que realmente no necesito comunicación TCP, ya que como indicaba, lanzo 'broadcast's' para que los programas que estuvieran activas pudieran tomar una actuación determinada ?

Qué otra solución habría que no fuera utilizar los componentes de Indy y basarme en cualquier otra estructura ?

Gracias una vez más a todos por vuestros comentarios.

Saludos

Casimiro Notevi 09-01-2013 18:48:39

Cita:

Empezado por gluglu (Mensaje 453124)
Cuanto tiempo sin aparecer por aquí !!! :o
Saludos a todos en primer lugar.

Saludos, tanto tiempo :)

No conozco la solución a tu duda :(

movorack 09-01-2013 19:36:35

Hola gluglu,

Te respondo de lo que encontré en internet.

Al parecer podrias usar Named Pipes o Sockets

Sockets: MultiCast Messages to multiple clients on the same machine

Named Pipes: Named Pipes unit for Delphi
Cita:

A named pipe is a named, one-way or duplex pipe for communication between the pipe server and one or more pipe clients. All instances of a named pipe share the same pipe name, but each instance has its own buffers and handles, and provides a separate conduit for client/server communication. The use of instances enables multiple pipe clients to use the same named pipe simultaneously.
Any process can access named pipes, subject to security checks, making named pipes an easy form of communication between related or unrelated processes.
Any process can act as both a server and a client, making peer-to-peer communication possible. As used here, the term pipe server refers to a process that creates a named pipe, and the term pipe client refers to a process that connects to an instance of a named pipe. The server-side function for instantiating a named pipe is CreateNamedPipe. The server-side function for accepting a connection is ConnectNamedPipe. A client process connects to a named pipe by using the CreateFile or CallNamedPipe function.
Named pipes can be used to provide communication between processes on the same computer or between processes on different computers across a network. If the server service is running, all named pipes are accessible remotely. If you intend to use a named pipe locally only, deny access to NT AUTHORITY\NETWORK or switch to local RPC.
For more information, leer mas

Julián 09-01-2013 21:21:36

Cita:

Empezado por gluglu (Mensaje 453124)
Cuanto tiempo sin aparecer por aquí !!! :o

Todo eso funcionaba perfectamente hasta que a algunos clientes se les está ocurriendo (para bien) montar la aplicación en servidores virtuales y accediendo con Escritorio Remoto.

En este caso, tengo múltiples instancias de mi aplicación corriendo a la vez en la misma máquina, y no puedo abrir el puerto UDP que haya asignado en dos instancias diferentes DENTRO DE LA MISMA MAQUINA. Y ahí es donde empiezan mis problemas de comunicación entre aplicaciones ....


Se supone que cada servidor virtual tiene su propia dirección IP, suponiendo que estemos hablando de vmware esx server o cosas así.
Si en una misma maquina hay, por ejemplo, dos conexiones con escritorio remoto, cada una de ellas será a una maquina virtual distinta, con distinta ip, y eso no son instancias diferentes en la misma maquina, sino en distintas, aunque sean virtuales. Por tanto no necesitarías tocar nada de tu aplicación.

A no ser que te refieras a otra cosa, claro.

Un saludo!

gluglu 09-01-2013 21:31:50

Gracias a todos por vuestras respuestas.

Movorack : Ya había visto todos esos links antes de postear aquí pero no me dan la solución que necesito.

Julián : Efectivamente, eso pensaba yo también. Pero no es así. Cuando en una segunda instancia (o sucesivas...) el programa intenta de nuevo abrir el puerto que está configurado, me da error indicando que es puerto ya está en uso.

Independientemente de que fuera un servidor virtual de una manera u otra, también tengo varias instalaciones funcionando así en Windows Server 2003/2008. Creo que esta última máquina virtual que comento estaba montada con Server 2008. En todos los casos, el error es el mismo. Además incluso, teniendo ya esta oportunidad, he configurado varias máquinas con Windows XP o W7 para acceso por escritorio remoto.

El problema viene a ser el mismo. Puede haber dos o más instancias que se estén ejecutando a la vez, sobre el mismo servidor, con sesiones remotas, y no me permite abrir el mismo puerto en dos instancias diferentes simultáneas.

El sistema que tengo montado, se basa (entre otras funcionalidades) que todo el mundo puede avisar a todo el mundo, sin que se establezcan protocolos TCP concretos, por eso utilizo UDP. Y además incluso hay dos programas diferentes (que pueden correr en la misma máquina) que también se comunican a través de UDP. Respecto de los dos programas diferentes lo tenía perfectamente resuelto con 2 puertos diferentes, y así no hay conflictos.

Pero claro, esa es la cuestión. Que cuando es una misma máquina, sí que me aparecen conflictos.

Gracias de nuevo

Al González 09-01-2013 21:50:17

A bote pronto se me ocurre que puedes usar memoria compartida (shared memory) para poner en ella "el mensaje". Y que las otras aplicaciones, mediante un temporizador (timer) o un hilo alterno (thread), consulten constantemente el contenido de ese bloque de memoria compartida.

Por cierto, ¿qué versión de Delphi usas?

Fuera de tema: Es un gusto verte de nuevo en los foros gluglu. :) ^\||/

gluglu 09-01-2013 21:57:34

Gracias de nuevo también por los mensajes de satisfacción por verme de nuevo por aquí ! :rolleyes:

La solución de la "memoria compartida" también la he pensado pero tendría que hacer alguna consideración adicional para saber si en un caso debo de utilizar una opción y en otro caso, otra opción. Aun así, realmente tampoco es la solución definitiva porque incluso se podrían dar (y de hecho se dan) instalaciones con redes locales donde a uno de los equipos se conectan mediante escritorio remoto y ejecutan una segunda instancia, por lo que es complicado llevar el control de todo ello, compaginando ambas opciones de solución.

Incluso había pensado en una solución más 'trivial' que es que cuando haya que mandar un mensaje por UDP, se lleve un 'control' (por ejemplo por la base de datos que es compartida) de los diferentes puertos UDP (uno por cada 'instancia') que están 'activos' y mandar el mismo mensaje en un bucle a todos los puertos UDP que estén configurados. De esta manera en una red local, cada puesto tendría el mismo puerto UDP, pero en una máquina con escritorio remoto, habría dos puertos UDP diferentes, y de esta manera, en realidad, el Broadcast sólo se haría a 2 puertos UDP.

En la máquina virtual configurada con los problemas del inicio de este hilo, hay 10 usuarios remotos creados, por lo que se 'solucionaría' con 10 puertos UDP diferentes y un bucle que envíe broadcast a esos 10 puertos. Es una solución creo que 'chapucilla', por eso intentaba encontrar algo 'bien hecho'.

También he visto por ahí la opción del SO_REUSEADDR, pero entiendo que esa opción de socket sólo está disponible para los clientes, y no para los servers, por lo que creo que tampoco me sirve realmente, porque tiene que haber varios servers, uno por cada instancia, para que cada uno esté igualmente escuchando.

Ahora mismo estoy con Delphi XE2.

;)

gluglu 09-01-2013 22:42:20

Alguien me podría ayudar con esta información, por favor ?

Ni sé cómo se podría aplicar el Patch que indican, ni sé cómo se podría actualizar a alguna versión de Indy que contenga las modificaciones que indica Remy Lebeau.

Gracias

Añado : Más Información

movorack 09-01-2013 23:03:19

Ese patch, creo puede ser de un sistema de versionamiento (git, mercurial, svn). si lo abres verás que es la información de como modificar un archivo del repositorio de Indy.

Podrías probar bajandote el fuente de los indy o desde el mismo SVN.

gluglu 09-01-2013 23:41:25

Como indicaba en mi post anterior, aquí se indican las modificaciones a realizar en los fuentes de Indy.

Lo he hecho todo de acuerdo a lo indicado. Incluso aquí está la versión completa de IdUDPServer.pas, pero al intentar recompilar los componentes Indy, me lanza el error :

[DCC Error] IdUDPServer.pas(153): E2147 Property 'ReuseSocket' does not exist in base class

:(

gluglu 09-01-2013 23:56:15

En el Patch, el código es correcto. En la página que indicaba en el último post, las indicaciones y el fichero IdUDPServer.pas no son correctos.

gluglu 10-01-2013 00:18:54

Bueno, .... pues tampoco.

La última versión de Indy10, como indica movorack, está en el link que adjunta.

Observando ahí la unidad IdUDPServer.pas, el código sí que es el que se describía anteriormente.

.... hay que ser un experto (al menos parece que yo no lo soy) para instalar la última versión se Indy (sustituirla por la que tengo actualmente). A ver si lo consigo .... cualquier ayuda es bienvenida !

gluglu 10-01-2013 01:00:01

... ni con esto me aclaro lo suficiente .... :(

WkaymQ48 10-01-2013 01:43:11

Se me ocurre una solución ...

Crear una aplicación que se ejecute como un servicio de windows, de este modo solo habrá una instancia independientemente de los clientes que se conecten por escritorio remoto. Esta aplicación seria la que este a la escucha por un puerto UDP, y ademas tambien tendria que esperar conexiones por un puerto TCP. Su funcionamiento seria sencillo, los clientes se conectan por el puerto TCP y mantienen activa la conexión esperando a que llegue algún mensaje, por su parte cuando llega algun mensaje por el puerto UDP lo reenviamos a todos los clientes que estén conectados por TCP.

Esta solución tiene varias ventajas:
  • No tienes que varias prácticamente el funcionamiento de tu aplicación
  • Es muy sencilla de montar
  • Se puede montar en una sola maquina o en varios puestos, solo hay que asegurarse de que cada puesto tiene su servidor montado

Saludos

WkaymQ48 10-01-2013 02:15:33

Se me ocurre otra solución más :) ...

quizá aun mas sencilla de implementar. Volvemos a la idea de un servicio por equipo, pero ahora el servicio solo escucha por un puerto UDP (en un puerto predeterminado). Cada vez que recibe un mensaje, guarda la IP y el puerto de origen en una lista y luego reenvía el mensaje a todos los demás que se encuentren en esa lista y se encuentren en el mismo equipo.

Tu aplicación solamente tendría que ponerse a la escucha por un puerto UDP (uno aleatorio que este libre) y desde ese mismo puerto enviar los mensajes a la dirección de broadcast, al puerto predeterminado, como hacia antes. El primer mensaje lo podemos enviar nada mas arrancar, solamente para darnos de alta en la lista, y a partir de hay recibiremos los siguientes mensajes que envíen los demás.

Es importante que el servicio solo reenvíe los mensajes a las direcciones IP de su propio equipo, de lo contrario si tenemos el servicio en varios equipos podríamos encontramos en un bucle de mensajes broadcast de un equipo a otro.

... le seguiré dando vueltas ... puede que se me ocurra algo mas :D

fjcg02 10-01-2013 13:16:21

Buenas,

no hay mucha más vuelta que dar que la que indica WkaymQ48.

Cuando abres dos sesiones en el mismo equipo vía terminal S, la primera instancia del equipo captura el puerto, y las siguientes se encuentran con el puerto bloqueado porque quieren utilizar el mismo puerto de la misma máquina.

En este caso, cada sesión no tiene una ip propia, es la misma ya que las sesiones son en el mismo equipo. Si tuviesemos equipos virtualizados ( vmware, virtual server, ... ) sí funcionaría, ya que abrimos equipos completos aunque se ejecuten en la misma máquina. Cada uno de ellos tiene su propia ip y funcionaría.

Por lo tanto, la solución pasaría por hacer un proceso, servicio o similar que se ejecute en cada equipo y que sea ése quien gestione los mensajes udp. Ahora, tendría que tener algún mecanismo para saber cuantas instancias de la aplicación tiene abiertas para que todas se enteren. Otra solución es la que has comentado, hacer una tabla de equipos y puertos y gestionar las comunicaciones vía tabla. Para eso puede ser más sencillo poner un timer y que lea la tabla para que cada aplicación sepa si tiene que refrescarse o hacer algo. Sigues teniendo el problema de que debes saber cuantas aplicaciones tienes abiertas por equipo ( ip + usuario por ejemplo ).

Un saludo

hgiacobone 10-04-2017 00:23:03

Amigo GluGlu, finalmente has encontrado alguna solución a esto?... Estoy por pasar por este mismo inconveniente.

Tengo una aplicación "MyApp.exe" donde cada usuario levanta una instancia de ella, con conexiones de Escritorio Remoto. Incluso, en la sesion del Administrador en el propio servidor (WinServer2008) ciertas veces existe tambien otra instancia en ejecución de dicho programa.

Además de esto, un segundo ejecutable "Monitor.exe", está siempre en funcionamiento en la sesion del Administrador y realiza un proceso donde las distintas etapas del mismo deben ser notificadas a la/las instancias del programa "MyApp.exe"

Actualmente lo tenía resuelto mediante el uso de Messages de Windows, esto es:
Desde el programa "Monitor.exe" cada vez que se necesita notificar algo, se invoca la función SendAppMessage() que hace esto:
Código Delphi [-]
type
    TMonitor1: class(TForm)
    ...
    private
    function SendAppMessage(mensaje:string):Boolean;
    ...
end;
///--------------------------------------
function TMonitor.SendAppMessage(mensaje:string):Boolean;
var
 CopyDataStruct : TCopyDataStruct;
 hReceptor: THandle;
 Respuesta: integer;
begin
  Result:= False;
  CopyDataStruct.dwData := 0; //utilizado para identificar el contenido del mensaje
  CopyDataStruct.cbData := Length( mensaje )+1;
  CopyDataStruct.lpData := PChar( mensaje );
  // Comprobamos si existe el receptor
  hReceptor := FindWindow( PChar('CORE') , nil );
  if hReceptor = 0 then
  begin
   {ShowMessage( 'No se ha encontrado al receptor.' );}
   Exit;
  end;
  // Enviamos el mensaje
  Respuesta := SendMessage( hReceptor, WM_COPYDATA, Integer( Handle ),
   Integer( @CopyDataStruct ) ) ;
// si nos interesa recogemos la respuesta del receptor
  {if Respuesta = 1 then ShowMessage( 'El mensaje ha sido recibido satisfactoriamente.' );}
  Result := (Respuesta = 1);
end;

//Ejemplo:
    if (sTime='18:00') then SendAppMessage('Happy Hour... vamos por una cerveza fría !');

Por otro lado, el programa "MyApp.exe", siempre está "a la escucha" de esos mensajes mediante un evento que se dispara de forma automática cuando aparece un mensaje invocando al proceso RecibirAppAmessage() que es este:
Código Delphi [-]
type
    TForm1: class(TForm)
    ...
    procedure RecibirAppMessage( var Msg: TWMCopyData ); message WM_COPYDATA;
    ...
end;
///------------------------
procedure TForm1.RecibirAppMessage( var Msg: TWMCopyData );
var
  s1:string;
begin
  s1:= PChar( Msg.CopyDataStruct.lpData );
  if bShowMessage 
    then ShowMessage( s1 )
    else StatusBar1.Panels[3].Text := s1;
  Msg.Result := 1; //como respuesta que se ha recibido el mensaje
end;



Lo malo de esto es que, una vez que "MyApp.exe" "publica" el mensaje, lo toma la primera sesion abierta de "MyApp.exe" que logra mostrar el mensaje en su pantalla, pero el resto de las instancias no reciben nada. O sea, lo consume la primera instancia abierta y "se borra" para el resto.

Debido a esto, estaba pensando en realizar mensajes UDP, pero me encuentro con el problema planteado aqui de las instancias de Escritorio Remoto y el problema de la apertura del PORT.

Como has resuelto esto?

escafandra 10-04-2017 00:39:04

El problema lo tienes porque usas FindWindow para localizar el Handle de la ventana donde enviarás el mensaje. Debes enumerarlas todas y enviar el mensaje a las que correspondan con tu App, normalmente las conocerás por el nombre de la clase de ventana. Revisa EnumWindows y GetClassName.


Saludos.

hgiacobone 10-04-2017 21:16:16

Cita:

Empezado por escafandra (Mensaje 515415)
El problema lo tienes porque usas FindWindow para localizar el Handle de la ventana...

Gracias por responder escafandra, pero sucede que el Handle es unico, me refiero que es un unico EXE.
Todas las instancias de RDP levantan el mismo "MyApp.exe". En el ejemplo quedó el verdadero nombre, pero digamos que lo que se intenta con esa linea es enviar el mensaje a ese EXE y no a otro.
Sería algomo como esto:
Código Delphi [-]
hReceptor := FindWindow( PChar('MyApp') , nil );
Tal vez esto no sea la mejor solución con RDP y es por eso mi consulta, para saber si hay algo mejor.:D

escafandra 10-04-2017 21:44:21

Cita:

Empezado por hgiacobone (Mensaje 515448)
Todas las instancias de RDP levantan el mismo "MyApp.exe". En el ejemplo quedó el verdadero nombre, pero digamos que lo que se intenta con esa linea es enviar el mensaje a ese EXE y no a otro.

Cuando envías un mensaje lo haces a un thread o a una ventana, no a un ejecutable. Cuando tienes varias instancias de un ejecutable, cada una con ventanas, los Handles no son los mismos. Una forma de enviar un mensaje a todas las ventanas es usar como Handle HWND_BROADCAST, pero entonces tendrás que tener un filtro en MyApp

Otra solución con sockets es implementar en MyApp un hilo con un cliente y que Monitor.exe sea un servidor. Si el protocolo es UDP no hace falta conexión previa u puedes enviar un datagrama UDP a la IP Broadcast con lo que todas las instancias de MyApp, lo recibirán. Previamente has de calcular la dirección Broadcast de tu red.


Saludos.

hgiacobone 10-04-2017 23:48:52

Cita:

Empezado por escafandra (Mensaje 515450)
...Cuando tienes varias instancias de un ejecutable, cada una con ventanas, los Handles no son los mismos...

Ah, ok.... o sea, Windows tomará un ID diferente aunque sea el mismo EXE?

Cita:

Empezado por escafandra (Mensaje 515450)
...Si el protocolo es UDP no hace falta conexión previa u puedes enviar un datagrama UDP a la IP Broadcast con lo que todas las instancias de MyApp, lo recibirán...

Entiendo, pero lo planteado en este foro hizo que dudara por el uso del port.
¿Entoneces es posible utilizar UDP aunque haya varias instancias RDP ?

escafandra 12-04-2017 00:52:15

Es posible tener varios servidores escuchando en la misma máquina por el mismo puerto, no es una situación común pero no es un imposible. Para esto está el Multicast.

Te pongo un ejemplo de servidor socket multicast en un thread para poder compaginarlo con una app de ventanas:
Código Delphi [-]
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, WinSock;

type
  TServer = class(TThread)
  private
  protected
    procedure Execute; override;
  public
  end;


  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    Server: TServer;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}



//---------------------------------------------------------------------------
// Establecer y ejecutar Timeout en winsock recv / recvfrom
// retorna como winSock.Select
//  -1: si error
//   0: fuera de tiempo
// > 0: Datos listos para leer
function TimeoutSocket(Sock: WinSock.TSOCKET; TimeOut: integer): integer;
var
  FDSet: WinSock.TFDSET;
  TimeVal: WinSock.TTIMEVAL;
begin
  TimeVal.tv_sec:= TimeOut div 1000;
  TimeVal.tv_usec:= TimeOut mod 1000;
  FD_ZERO(FDSet);
  FD_SET(Sock, FDSet);
  Result:= WinSock.Select(0, @FDSet, nil, nil, @TimeVal)
end;


procedure TServer.Execute;
const
  BufferSize = 1024;
  Port = 9090;
var
  WSA: WinSock.TWSADATA;
  Sock: WinSock.TSOCKET;
  Addr: WinSock.sockaddr_in;
  Buffer: array[0..BufferSize-1] of AnsiChar;
  Len, AddrSize: integer;
  dwTime: DWORD;
  ValMulticast: AnsiCHAR;
begin
  if (WinSock.WSAStartup(MakeWord(2, 2), WSA) <> 0) then exit;
  Sock := WinSock.socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
  if (Sock <> INVALID_SOCKET) then
  begin
    dwTime:= 1000;
    ValMulticast:= #1;
    setsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, PCHAR(@dwTime), sizeof(dwTime));
    setsockopt(Sock, SOL_SOCKET, SO_REUSEADDR, @ValMulticast, sizeof(ValMulticast));
    setsockopt(Sock, IPPROTO_IP, IP_MULTICAST_LOOP, 0, 1);
    Addr.sin_family := AF_INET;
    Addr.sin_addr.s_addr := INADDR_ANY;
    Addr.sin_port := WinSock.htons(Port);
    AddrSize := sizeof(Addr);

    // Asociamos el socket al puerto y a escuchar
    if (bind(Sock, Addr, AddrSize) <> -1) then
    begin
      // Bucle de escucha...
      while not Terminated do
      begin
        Len := 0;

        // Comprobamos si ha recibido algun mensaje que leer
        if TimeoutSocket(Sock, 500) > 0 then
        begin
          ZeroMemory(@Buffer[0], BufferSize);
          Len := WinSock.recvfrom(Sock, Buffer, BufferSize-1, 0, Addr, AddrSize);
          // Leemos el paquete enviado
          if (Len > 0) and (Len < BufferSize) then
          begin
            Windows.Beep(1000, 100);
            MessageBox(0, Buffer, 'Eureka',0);
          end;
        end;
      end;
    end;
    WinSock.closesocket(Sock);
  end;
  WinSock.WSACleanUp;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Server:= TServer.Create(false);
end;

end.

Este servidor se pone a la escucha en el puerto 9090 y podemos tener varios en ejecución.

Ahora el cliente debe enviar mensajes UDP al Broadcast por el puerto 9090, para ello debemos calcular esa dirección. Te pongo un ejemplo de cliente que envía un mensaje a todos los servidores a la vez, estén o no en la misma máquina:
Código Delphi [-]
unit Unit2;

interface

uses
  Windows, WinSock, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

TMIB_IPADDRROW = packed record
  dwAddr: DWORD;
  dwIndex: DWORD;
  dwMask: DWORD;
  dwBCastAddr: DWORD;
  dwReasmSize: DWORD;
  unused1: SmallInt;
  wType: SmallInt;
end;

TMIB_IPADDRTABLE = record
  dwNumEntries: DWORD;
  table: array[0..0] of TMIB_IPADDRROW;
end;
PMIB_IPADDRTABLE = ^TMIB_IPADDRTABLE;


function GetIpAddrTable(IpAddrTable: PMIB_IPADDRTABLE; pdwSize: PULONG;
  Order: BOOL): DWORD; stdcall; external 'iphlpapi.dll' name 'GetIpAddrTable';

var
  Form2: TForm2;

implementation

{$R *.dfm}
function GetCurrentIP: DWORD;
var
  Wsa: WSADATA;
  Name: array[0..255] of char;
  hostinfo: PHOSTENT;
begin
  Result:= 0;
  FillChar(Wsa, SizeOf(WSAData), 0);
  if WSAStartup(MAKEWORD(2, 2), Wsa) = 0 then
  begin
    if gethostname(Name, SizeOf(Name)) = 0 then
    begin
      hostinfo:= gethostbyname(Name);
      if hostinfo <> nil then
        Result:= PDWORD(hostinfo^.h_addr_list^)^;
    WSACleanup;
    end;
  end;
end; 
 
function GetBrodcastAddress: String;
var
  pIPAddrTable: PMIB_IPADDRTABLE;
  dwSize: DWORD;
  i: integer;
  BroadCastInAddr: IN_ADDR;
begin
  BroadCastInAddr.S_addr:= 0;
  dwSize:= 0;
  GetIpAddrTable(nil, @dwSize, true);
  GetMem(pIPAddrTable, dwSize);
  if pIPAddrTable<>nil then
  begin
    if GetIpAddrTable(pIPAddrTable, @dwSize, true) = NO_ERROR then
      for i:=0 to  pIPAddrTable^.dwNumEntries-1 do
      begin
        if GetCurrentIP = pIPAddrTable^.table[i].dwAddr then
        begin
          BroadCastInAddr.S_addr:= pIPAddrTable^.table[i].dwAddr or not pIPAddrTable^.table[i].dwMask;
          break;
        end;
      end;
    FreeMem(pIPAddrTable);
  end;
  Result:= inet_ntoa(BroadCastInAddr);
end;

procedure SendUDP(Msg: AnsiString; IP: String; Port: WORD);
var
 Wsa: WSADATA;
 S: TSocket;
 Addr: WinSock.sockaddr_in;
 Host: PHostent;
 IPAddr: ^integer;
begin
  if WSAStartup(MAKEWORD(2, 2), Wsa) = 0 then
  try
    S:= Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if S <> INVALID_SOCKET then
    begin
      Host:= gethostbyname(PAnsiCHAR(IP));
      IPAddr:= @Host.h_addr_list^[0];
      Addr.sin_family:= AF_INET;
      Addr.sin_addr.S_addr:= IPAddr^;
      Addr.sin_port:= htons(Port);
      Sendto(S, PAnsiChar(Msg)^, Length(Msg), 0, Addr, SizeOf(sockaddr_in));
     end;
  finally
    WSACleanup();
  end;
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  SendUDP('Hola', GetBrodcastAddress, 9090);
end;

end.

Probado en Win10 y compilado con Delphi 7


Espero que resuelva tu duda y en general la duda que originó ente hilo.


Saludos.

hgiacobone 13-04-2017 16:24:50

Muchas, pero muchas, gracias a migo escafandra... pero esto es muy elevado para mi entendimiento :(

Primero, porque creo yo tengo un error de concepto:
-En este caso, existe un pequeño ejecutable que monitorea un proceso. Llamemos a este "2do.exe"
-Luego está la aplicacion principal, que cada usurario levanta mediante una conexion RDP (Windows). Llamemos "1ro.exe". O sea, hay varios "1ro.exe" y un solo "2do.exe"

Entonces, el programa monitor (2do.exe) cada vez que realiza una operación, debería enviar a modo de mensaje de texto, ese resultado a cada instancia del programa (1ro.exe) y este, cuando lo reciba, lo mostrará en pantalla o como se prefiera. Yo pensaba que el monitor debería ser el Servidor y las otras aplicaciones los clientes, pero parece que es a la inversa, no? :confused:

Segundo, observo que utilizas la unidad WinSock. ¿Es una unidad estandar de Delphi o solo si adquieres el paquete Indy de las versiones Archictect/Professional?

Tercero: he instalado por sugerencia de varios, unos componentes "free" denominados Overbyte ICS que tienen infinidad de componentes de conexion TCP/UDP entre ellos:
> OverbyteIcsWSocket.pas Winsock component - TCP, UDP, DNS,...
> OverbyteIcsWSocketE.pas Register procedure and property editor for TWSocket
> OverbyteIcsWSocketS.pas Winsock component for building servers
> OverbyteIcsWSocketTS.pas Winsock component for building multithreaded servers
> OverbyteIcsDnsQuery DNS lookup component - useful for getting MX records

...que aun no se ni cómo utilizarlos.
¿Supones que podría reemplazar los WinSocks por ellos o no es conveniente?

escafandra 13-04-2017 17:09:15

Cita:

Empezado por hgiacobone (Mensaje 515534)
Entonces, el programa monitor (2do.exe) cada vez que realiza una operación, debería enviar a modo de mensaje de texto, ese resultado a cada instancia del programa (1ro.exe) y este, cuando lo reciba, lo mostrará en pantalla o como se prefiera. Yo pensaba que el monitor debería ser el Servidor y las otras aplicaciones los clientes, pero parece que es a la inversa, no? :confused:

En principio sujerí que monitor fuese el servidor para simplificar la conexión pero eso requiere que los clientes pregunten al servidor cada cierto tiempo alejándose de la idea de que sea el Monitor el que envíe mensajes cuando le plazca a las instancias del 1r0.exe que deben estar a la escucha. Esto requiere que Monitor actúe como cliente y el resto como servidores a la escucha por el mismo puerto. Esta idea es la que, en principio, va en contra de la teoría que dice que "solo puede haber un servidor escuchando en un mismo puerto", que es cierta salvo alguna excepción como la que muestro.

Cita:

Empezado por hgiacobone (Mensaje 515534)
Segundo, observo que utilizas la unidad WinSock. ¿Es una unidad estandar de Delphi o solo si adquieres el paquete Indy de las versiones Archictect/Professional?

La API WinSock es una unidad estándar de windows. Los Sockets son la vía a bajo nivel que el S.O. nos brinda para manejar los paquetes en la red permitiéndonos enlazar la capa de aplicación con la de transporte. Son un estándar básico adoptado pos casi todos los S.O. por lo que te sirve para conectar con cualquier máquina en la red. Como es natural, Delphi y otros lenguajes, son capaces de manejar esa API. Los componentes de terceros para comunicación por red, usan winsock, como no podía ser de otra manera, para realizar sus propósitos.

Cita:

Empezado por hgiacobone (Mensaje 515534)
Tercero: he instalado por sugerencia de varios, unos componentes "free" denominados Overbyte ICS que tienen infinidad de componentes de conexion TCP/UDP entre ellos:
> OverbyteIcsWSocket.pas Winsock component - TCP, UDP, DNS,...
> OverbyteIcsWSocketE.pas Register procedure and property editor for TWSocket
> OverbyteIcsWSocketS.pas Winsock component for building servers
> OverbyteIcsWSocketTS.pas Winsock component for building multithreaded servers
> OverbyteIcsDnsQuery DNS lookup component - useful for getting MX records

...que aun no se ni cómo utilizarlos.
¿Supones que podría reemplazar los WinSocks por ellos o no es conveniente?

Como ya te digo, esos componentes no reemplazan, sino que usan internamente winsock. Para el uso concreto que se pide en este hilo, considero que la mejor forma (por poco usual) es trabajar a bajo nivel con sockets (winsock) para tener el control absoluto de lo que hacen y como se configuran (setsockopt)

Ignoro como quieres que 1ro.exe trabaje con los mensajes recibidos, por eso en el ejemplo puse un MessageBox. Ten en cuenta que todo el código servidor está en un Thread, lo que significa que no puedes usar a las bravas la VCL desde él. Para sincronizar el hilo con el formulario debes usar el método Synchronize o mejor aún en este caso, mensajes Windows desde el hilo a la ventana del formulario que elijas, para ello basta con que al crear el hilo le pases el Handle de dicha ventana y uses SendMessage desde el hilo, cuando recibas un mensaje UDP (que viene de Monitor.exe). En tu formulario generas una función de tratamiento de esos mensajes personalizados y funcionará como un evento.

Saludos.

escafandra 13-04-2017 17:48:48

A modo de ilustración, te muestro el servidor modificado para disparar un evento al recibir un mensaje desde el cliente. En este caso, el formulario incorpora un TLabel donse se escribirá el mensaje de texto recibido. Uso un mensaje de Windows pero también podrías modificarlo para usar Synchronize.

Código Delphi [-]
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, WinSock, StdCtrls;

const
  WM_MSG = WM_USER + 1;

type
  TServer = class(TThread)
  private
    FWND: HWND;
  protected
    procedure Execute; override;
  public
    constructor Create(const WND: HWND);
  end;


  TForm1 = class(TForm)
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
  private
    Server: TServer;
    procedure OnMsg(var Msg: TMessage); message WM_MSG;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
constructor TServer.Create(const WND: HWND);
begin
  FWND:= WND;
  inherited Create(false);
end;

procedure TServer.Execute;
const
  BufferSize = 1024;
  Port = 9090;
var
  WSA: WinSock.TWSADATA;
  Sock: WinSock.TSOCKET;
  Addr: WinSock.sockaddr_in;
  Buffer: array[0..BufferSize-1] of AnsiChar;
  Len, AddrSize: integer;
  dwTime: DWORD;
  ValMulticast: AnsiCHAR;
begin
  if (WinSock.WSAStartup(MakeWord(2, 2), WSA) <> 0) then exit;
  Sock := WinSock.socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
  if (Sock <> INVALID_SOCKET) then
  begin
    dwTime:= 1000;
    ValMulticast:= #1;
    // Establecemos un timeout de bloqueo
    WinSock.setsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, PAnsiCHAR(@dwTime), sizeof(dwTime));
    // Permitimos vincular el puerto más veces
    WinSock.setsockopt(Sock, SOL_SOCKET, SO_REUSEADDR, @ValMulticast, sizeof(ValMulticast));
    // Establecemos la opción multicast(Activa por defecto)
    WinSock.setsockopt(Sock, IPPROTO_IP, IP_MULTICAST_LOOP, nil, 1);
    Addr.sin_family := AF_INET;
    Addr.sin_addr.s_addr := INADDR_ANY;
    Addr.sin_port := WinSock.htons(Port);
    AddrSize := sizeof(Addr);

    // Asociamos el socket al puerto y a escuchar
    if (bind(Sock, Addr, AddrSize) <> -1) then
    begin
      // Bucle de escucha...
      while not Terminated do
      begin
        ZeroMemory(@Buffer[0], BufferSize);
        Len := WinSock.recvfrom(Sock, Buffer, BufferSize-1, 0, Addr, AddrSize);
        // Leemos el paquete enviado
        if (Len > 0) and (Len < BufferSize) then
        begin
          Windows.Beep(1000, 100);
          // Se lo comunicamos al formulario
          Windows.SendMessage(FWND, WM_MSG, WPARAM(@Buffer[0]), 0);
        end;
      end;
    end;
    WinSock.closesocket(Sock);
  end;
  WinSock.WSACleanUp;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Server:= TServer.Create(Handle);
end;

// Respondiendo al mensaje recibido por el Thread servidor
procedure TForm1.OnMsg(var Msg: TMessage);
begin
  Label1.Caption:= PAnsiChar(Msg.WParam);
end;

end.


Saludos

hgiacobone 13-04-2017 18:50:23

Cita:

Empezado por escafandra (Mensaje 515536)
A modo de ilustración, te muestro el servidor modificado para disparar un evento al recibir un mensaje desde el cliente. En este caso, el formulario incorpora un TLabel donse se escribirá el mensaje de texto recibido. Uso un mensaje de Windows pero también podrías modificarlo para usar Synchronize. Saludos

Muchas gracias nuevamente.
Ya generé ambos ejemplos, el cliente y el server, pero no pasa nada...
Inicio primero el Server y luego el cliente, hago clic en el Button1 y nada....
Inicio primero el Cliente y luego el Server, hago clic en el Button1 y nada....
Mi LAN es 192.168.14.xxx y la funcion GetBrodcastAddress; está devolviendo correctamente 192.168.14.255
Probé otros Port y nada...
que puede ser ?

escafandra 13-04-2017 19:07:09

3 Archivos Adjunto(s)
Posiblemente dependa de la versión del delphi que uses.
La versión Cliente que he probado con delphi7 y Berlin es esta:
Código Delphi [-]
unit Unit2;

interface

uses
  Windows, WinSock, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

TMIB_IPADDRROW = packed record
  dwAddr: DWORD;
  dwIndex: DWORD;
  dwMask: DWORD;
  dwBCastAddr: DWORD;
  dwReasmSize: DWORD;
  unused1: SmallInt;
  wType: SmallInt;
end;

TMIB_IPADDRTABLE = record
  dwNumEntries: DWORD;
  table: array[0..0] of TMIB_IPADDRROW;
end;
PMIB_IPADDRTABLE = ^TMIB_IPADDRTABLE;


function GetIpAddrTable(IpAddrTable: PMIB_IPADDRTABLE; pdwSize: PULONG;
  Order: BOOL): DWORD; stdcall; external 'iphlpapi.dll' name 'GetIpAddrTable';

var
  Form2: TForm2;

implementation

{$R *.dfm}
function GetCurrentIP: DWORD;
var
  Wsa: WSADATA;
  Name: array[0..255] of AnsiChar;
  hostinfo: PHOSTENT;
begin
  Result:= 0;
  FillChar(Wsa, SizeOf(WSAData), 0);
  if WSAStartup(MAKEWORD(2, 2), Wsa) = 0 then
  begin
    if gethostname(Name, SizeOf(Name)) = 0 then
    begin
      hostinfo:= gethostbyname(Name);
      if hostinfo <> nil then
        Result:= PDWORD(hostinfo^.h_addr_list^)^;
    WSACleanup;
    end;
  end;
end; 
 
function GetBrodcastAddress: AnsiString;
var
  pIPAddrTable: PMIB_IPADDRTABLE;
  dwSize: DWORD;
  i: integer;
  BroadCastInAddr: IN_ADDR;
begin
  BroadCastInAddr.S_addr:= 0;
  dwSize:= 0;
  GetIpAddrTable(nil, @dwSize, true);
  GetMem(pIPAddrTable, dwSize);
  if pIPAddrTable<>nil then
  begin
    if GetIpAddrTable(pIPAddrTable, @dwSize, true) = NO_ERROR then
      for i:=0 to  pIPAddrTable^.dwNumEntries-1 do
      begin
        if GetCurrentIP = pIPAddrTable^.table[i].dwAddr then
        begin
          BroadCastInAddr.S_addr:= pIPAddrTable^.table[i].dwAddr or not pIPAddrTable^.table[i].dwMask;
          break;
        end;
      end;
    FreeMem(pIPAddrTable);
  end;
  Result:= inet_ntoa(BroadCastInAddr);
end;

procedure SendUDP(Msg: AnsiString; IP: AnsiString; Port: WORD);
var
 Wsa: WSADATA;
 S: TSocket;
 Addr: WinSock.sockaddr_in;
 Host: PHostent;
begin
  if WinSock.WSAStartup(MAKEWORD(2, 2), Wsa) = 0 then
  try
    S:= Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if S <> INVALID_SOCKET then
    begin
      Host:= WinSock.gethostbyname(PAnsiCHAR(IP));
      Addr.sin_family:= AF_INET;
      Addr.sin_addr.S_addr:= PInAddr(Host.h_addr_list^)^.S_addr;
      Addr.sin_port:= WinSock.htons(Port);
      WinSock.Sendto(S, PAnsiChar(Msg)^, Length(Msg), 0, Addr, SizeOf(sockaddr_in));
     end;
  finally
    WinSock.WSACleanup();
  end;
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  SendUDP('Hola', GetBrodcastAddress, 9090);
end;

end.


Ten en cuenta que el UNICODE juega malas pasadas. Subo un proyecto con fuentes y ejecutables compilados.

jars 17-04-2017 15:34:00

Excelente aporte Escafandra!!!

jars 17-04-2017 16:04:47

Escafandra, te hago el siguiente planteo que difiere del expuesto hasta ahora.
Yo tengo actualmente un programa que actúa como servidor usando TServerSocket en modo nonblocking que envía cada 2 segundos registros con el método SendBuf a cada uno de los clientes (TClientSocket) que se hayan conectado. El tamaño de cada paquete comprimido puede ser algo mas de 8k. Como suelen ser varias pc's clientes, esto genera bastante trabajo del lado servidor que ademas tiene que ir recolectando y haciendo muchos calculos, luego volcar la información a sus respectivos registros, comprimirlos y luego enviar a cada cliente. Si esto quisiera hacerlo con UDP, como debería ser ya que de este modo haría un solo envío?
Gracias.

escafandra 17-04-2017 18:35:52

Cita:

Empezado por jars (Mensaje 515563)
Escafandra, te hago el siguiente planteo que difiere del expuesto hasta ahora.
Yo tengo actualmente un programa que actúa como servidor usando TServerSocket en modo nonblocking que envía cada 2 segundos registros con el método SendBuf a cada uno de los clientes (TClientSocket) que se hayan conectado. El tamaño de cada paquete comprimido puede ser algo mas de 8k. Como suelen ser varias pc's clientes, esto genera bastante trabajo del lado servidor que ademas tiene que ir recolectando y haciendo muchos calculos, luego volcar la información a sus respectivos registros, comprimirlos y luego enviar a cada cliente. Si esto quisiera hacerlo con UDP, como debería ser ya que de este modo haría un solo envío?
Gracias.

1. Por lo que entiendo solo debes realizar los cálculos cada vez que vas a enviar y luego recorrer los clientes para enviarles exactamente lo mismo.

2. Si quieres hacer un solo envío de un paquete vía Broadcast, debes hacerlo como en el ejemplo que público. Un sólo envio a la dirección Broadcast previamente calculada desde el servidor. El ejemplo permite varios servidores y varios clientes sin cambiar el código, pero si quieres que los servidores envíen puedes establecer un bucle de comunicación o simplemente implementar el procedimiento SendUDP en cada uno. Esto requiere que cada cliente tenga un rhread a la 3scucha, es decir, también son servidores. Si las maquinas van a ser distintas no es preciso usar SO_REUSEADDR. Ten en cuenta que todas las app pueden ser clientes y servidores a un tiempo si quieres y que en UDP no precisas una conexión previa lo que es bueno por una parte pero no asegura que el paquete sea recibido.


Saludos

jars 17-04-2017 19:19:02

Y como se puede lidiar con paquetes que no llegan o cuando se fragmenta en mas de uno?

escafandra 17-04-2017 19:31:06

Cita:

Empezado por jars (Mensaje 515573)
Y como se puede lidiar con paquetes que no llegan o cuando se fragmenta en mas de uno?

Queda de mano del desarrollador. Realiza un checksum al enviar y al recibir y sabrás si el envío es correcto. En caso contrario habrà que pedir al servidor que lo repita, por ejemplo. La ventaja de TCP es que el protocolo asegura el envío.

Saludos.

jars 17-04-2017 19:33:32

Ok, entonces veo que es preferible seguir como hasta ahora con TCP.

jars 21-04-2017 16:35:42

Como sería esto mismo pero que el servidor envíe mensajes udp a clientes en distintas pc´s?

escafandra 21-04-2017 18:06:37

El esquema del ejemplo funciona con cualquier máquina en la Red con el mismo código. Un cliente que envía mensajes a servidores en red a través de la dirección BroadCast.


Saludos.


La franja horaria es GMT +2. Ahora son las 00:45:27.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi