A raíz de una que se reviviera un antiguo hilo en CD, Mensajes Broadcast vía UDP entre varias instancias corriendo en una misma máquina, publiqué un ejemplo que rompe el esquema ampliamente conocido que afirma que no podemos tener más de un servidor socket escuchando por el mismo puerto. En general la afirmación es cierta pero es posible asociar un mismo puerto a dos sockets en dos circunstancias que pasan por usar una opción especial: SO_REUSEADDR con la AP Isetsockopt. El uso de esta opción permite vincular el mismo puerto a dos sockets al mismo tiempo quedando de forma indeterminada quien de los dos realmente recibirá el mensaje salvo que estemos usando MultiCast, en cuyo caso ambos servidores recibirían el mensaje.
SO_REUSEADDR puede usarse maliciosamente para denegar un servicio de red debido a la indeterminación pero puede usarse para enviar un mensaje a varios servidores que escuchan en el mismo puerto como es el caso este tema, de tal forma que varias instancias de un ejecutable incluso corriendo en estaciones de ventana diferentes, pueden recibir el mensaje emitido por un cliente de red.
Me ha parecido interesante añadir la prueba de concepto en el foro. La prueba de concepto es simple, se trata de una aplicación servidor en un thread separado. esta aplicación vincula un socket UDP al un puerto elegido al azar, el 9090. podemos tener varias instancias ejecutándose. La otra aplicación es un cliente UDP que envía por el puerto 9090 a la dirección Broadcast de la red un mensaje simple: 'Hola'. Cuando los servidores lo reciban mostrarán el mensaje de texto con un MessageBox.
El código del servidor:
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} 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 setsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, PAnsiCHAR(@dwTime), sizeof(dwTime)); // Permitimos vincular el puerto más veces setsockopt(Sock, SOL_SOCKET, SO_REUSEADDR, @ValMulticast, sizeof(ValMulticast)); // Establecemos la opción multicast(Activa por defecto) 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); MessageBoxA(0, Buffer, 'Eureka',0); end; end; end; WinSock.closesocket(Sock); end; WinSock.WSACleanUp; end; procedure TForm1.FormCreate(Sender: TObject); begin Server:= TServer.Create(false); end; end.
El código del cliente:
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 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)); Addr.sin_family:= AF_INET; Addr.sin_addr.S_addr:= PInAddr(Host.h_addr_list^)^.S_addr; 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.
El código está probado con delphi7 y Berlin en Win8 y Win10
Subo el ejemplo.
Saludos.