Recibir mensajes por UDP

El siguiente ejemplo muestra como recibir mensajes a través de la red usando el protocolo UDP. Su funcionamiento es muy simple, una vez que se ejecuta se mantiene a la espera de recibir un paquete UDP por alguno de los puertos en los que escucha. Cuando recibe un mensaje lo muestra por pantalla, tanto en hexadecimal como en forma de texto.

El código es el siguiente (es una aplicación de consola):

program udp;
 
{$APPTYPE CONSOLE}
 
 
uses
  Windows, SysUtils, Winsock;
 
 
const
  START_PORT = 5000;
  END_PORT = 5005;
  BUFFER_SIZE = 64*1024;  // 64 Kb
 
var
  Terminar: Boolean;
  WSAData: TWSAData;
 
function Min(i,j: Integer): Integer;
begin
  if i < j then
    Result:= i
  else
    Result:= j;
end;
 
// Muestra el mensage en hexadecimal y como texto
procedure WriteHex(Buffer: PAnsiChar;  Count: Integer);
var
  i,j: Integer;
begin
  j:= 0;
  while Count > 0 do
  begin
    Write(IntToHex(j,8) + ':' + #32#32);
    for i:= 0 to Min(Count,8) - 1 do
      Write(IntToHex(Byte(Buffer[i]),2) + #32);
    Write(#32);
    for i:= 8 to Min(Count,16) - 1 do
      Write(IntToHex(Byte(Buffer[i]),2) + #32);
    for i:= Min(Count,16) to 15 do
      Write(#32#32#32);
    Write(#32 + '|');
    for i:= 0 to Min(Count,16) - 1 do
    if Char(Buffer[i]) in ['A'..'Z','a'..'z','0'..'9'] then
      Write(Buffer[i])
    else
      Write('.');
    Writeln('|');
    Dec(Count,16);
    inc(Buffer,16);
    inc(j,16);
  end;
end;
 
procedure Loop;
var
  i,j: Integer;
  b: Boolean;
  Addr: TSockaddr;
  AddrSize: Integer;
  FDSet: TFDSet;
  TimeVal: TTimeVal;
  Buffer: PAnsiChar;
  Sockets: array[START_PORT..END_PORT] OF TSocket;
begin
  // Si el puerto de inicio es mayor que el final terminamos
  if START_PORT > END_PORT then
    Exit;
  b:= FALSE;
  // Creamos un socket por cada puerto
  for i:= START_PORT to END_PORT do
  begin
    Sockets[i]:= Winsock.Socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if Sockets[i] <> INVALID_SOCKET then
    begin
      with Addr do
      begin
        sin_family:= AF_INET;
        sin_port:= htons(i);
        sin_addr.s_addr:= Inet_Addr(PChar('0.0.0.0'));
      end;
      // Ponemos cada socket a escuhar en el puerto correspondiente
      if Bind(Sockets[i], Addr, SizeOf(Addr)) = SOCKET_ERROR then
      begin
        // Si no podemos cerramos el socket
        CloseSocket(Sockets[i]);
        Sockets[i]:= INVALID_SOCKET;
      end else
        b:= TRUE;
    end;
  end;
  // Si no pudimos poner a la escucha ningun socket terminamos
  if not b then
    Exit;
  // Reservamos espacio para almacenar los mensajes
  GetMem(Buffer,BUFFER_SIZE);
  try
    // Mientras no pulsamos "Ctrl+C"
    while not Terminar do
    begin
      // Para cada socket
      for i:= START_PORT to END_PORT do
        if Sockets[i] <> INVALID_SOCKET then
        begin
          TimeVal.tv_sec:= 0;
          TimeVal.tv_usec:= 500;
          FD_ZERO(FDSet);
          FD_SET(Sockets[i], FDSet);
          // Comprobamos si ha recibido algun mensaje
          if Select(0, @FDSet, nil, nil, @TimeVal) > 0 then
          begin
            AddrSize:= Sizeof(Addr);
            FillChar(Buffer^,BUFFER_SIZE,#0);
            // Copiamos el mensaje en el buffer
            j:= Recvfrom(Sockets[i],Buffer^,BUFFER_SIZE,0,sockaddr_in(Addr),AddrSize);
            if j <> SOCKET_ERROR then
            begin
              // Imprimimos el mensaje
              Writeln('Puerto: ' + IntToStr(i) + ' IP: ' + inet_ntoa(Addr.sin_addr));
              writeln;
              WriteHex(Buffer,j);
              Writeln;
              Writeln;
            end;
          end;
        end;
      Sleep(10);
    end;
  finally
    FreeMem(Buffer);
  end;
  // Cerramos cada uno de los socket
  for i:= START_PORT to END_PORT do
  begin
    if Sockets[i] <> INVALID_SOCKET then
    begin
     CloseSocket(Sockets[i]);
    end;
  end;
end;
 
 
// Esta rutina maneja la señal "Ctrl+C"
function HandlerRoutine(dwCtrlType: DWORD): BOOL; stdcall;
begin
  Result:= TRUE;
  case dwCtrlType of
    CTRL_C_EVENT:
      Terminar:= TRUE;
    CTRL_CLOSE_EVENT:
      Terminar:= TRUE;
    CTRL_LOGOFF_EVENT:
      Terminar:= TRUE;
    CTRL_SHUTDOWN_EVENT:
      Terminar:= TRUE;
    else
      Result:= FALSE;
  end;
end;
 
begin
  Terminar:= FALSE;
  if SetConsoleCtrlHandler(@HandlerRoutine,TRUE) then
  begin
    Writeln('Pulsa Ctrl+C para salir ...');
    Writeln;
    FillChar(WSAData,SizeOf(WSAData),0);
    if WSAStartup(MAKEWORD(1, 1), WSADATA) = 0 then
    try
      Loop;
    finally
      WSACleanup();
    end;
  end;
  Writeln;
  Writeln('Adios!');
end.

Para probarlo podemos usar cualquier porgrama que permita enviar paquetes UDP.

Yo recomiendo ncat

ncat -u localhost 5001

Si usando el programa anterior enviamos un mensaje con la palabra "PRUEBA" repetida varias veces, obtenemos lo siguiente por pantalla:

Pulsa Ctrl+C para salir ...
 
Puerto: 5001 IP: 127.0.0.1
 
00000000:  50 52 55 45 42 41 20 50  52 55 45 42 41 20 50 52  |PRUEBA.PRUEBA.PR|
00000010:  55 45 42 41 20 50 52 55  45 42 41 20 50 52 55 45  |UEBA.PRUEBA.PRUE|
00000020:  42 41 20 50 52 55 45 42  41 20 50 52 55 45 42 41  |BA.PRUEBA.PRUEBA|
00000030:  20 50 52 55 45 42 41 20  50 52 55 45 42 41 20 50  |.PRUEBA.PRUEBA.P|
00000040:  52 55 45 42 41 20 50 52  55 45 42 41 20 50 52 55  |RUEBA.PRUEBA.PRU|
00000050:  45 42 41 20 50 52 55 45  42 41 20 50 52 55 45 42  |EBA.PRUEBA.PRUEB|
00000060:  41 20 50 52 55 45 42 41  20 50 52 55 45 42 41 0A  |A.PRUEBA.PRUEBA.|

Enlaces de interés:
http://es.wikipedia.org/wiki/User_Datagram_Protocol
http://nmap.org/ncat/