UDP (User Datagram Protocol)


Allgemeines

Das UDP ist ein

Protokoll der Transport-Schicht (also derselben Schicht wie TCP). Im Gegensatz zu IP können hier verschiedene UDP-Applikationen über die Port-Nummer unterschieden werden.

Port-Nummern

Wie bei TCP auch werden bei UDP Port-Nummern verwendet. Die Port-Nummern bis 1024 sind reserviert. Sie werden von der IANA (Internet Assigned Numbers Authority) http://www.iana.org für wichtige Protokolle vergeben. Die Port-Nummern für die gängigen Protokolle können im SERVICES-File eingesehen werden.

Die Port-Nummern von 1025 bis 32767 sind frei verfügbar für eigene Anwendungen.

Broadcasts

UDP-Pakete ("Datagramme") können auch per Broadcast an alle Hosts des lokalen Netzes versandt werden (die IP-Adresse des Empfängers ist dann 255.255.255.255). Auf diese Weise können recht einfach Systeme realisiert werden, die bestimmte Listener suchen (die melden sich auf einen solchen Broadcast dann mit einem entsprechenden Paket; alle anderen bekommen das Paket erst gar nicht) oder sich einfach an "alle" wenden.

Broadcast-Pakete gehen nur einmal über die Netzwerk-Leitung. Es wird also nicht für jeden Teilnehmer ein getrenntes Paket verschickt. Die Leitungs-Belastung ist also gleich wie beim Versenden eines Pakets an nur einen Peer.

Broadcasts müssen mit einem speziellen Aufruf erst "freigeschaltet" werden.

Programmierung

Listener

Ein UDP-Listener wird folgendermaßen aufgebaut:

Erzeugen des Socket (Socket)

VAR
  Sock : TSocket;         // Socket-Instanz
BEGIN
  Sock := Socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);  // SOCK_DGRAM  = Datagramm-orienierten Socket erzeugen
                                                      // IPPROTO_UDP = UDP-Protokoll verwenden
  IF Sock = INVALID_SOCKET THEN EXIT;

Binden des Socket an den Port (Bind)

Mit der Bind-Funktion wird ein Socket an den angegebenen Port gebunden, d. h. er kann nun auf diesem Port horchen.

VAR
  SockAddr : TSockAddr_In;    // Adress- und Port-Informationen
BEGIN
  FillChar (SockAddr, SizeOf (SockAddr), 0);       // Lauter NUL-Bytes
  SockAddr.sin_family      := AF_INET;             // Internet-Adressfamilie
  SockAddr.sin_Port        := HtoNs (PortNummer);  // Port-Nummer in Network Byte Order
  SockAddr.sin_Addr.S_Addr := INADDR_ANY;          // Empfangen von allen Adressen

  Bind (Sock, @SockAddr, SizeOf (SockAddr));

Prüfen, ob Pakete eingegangen sind (IoctlSocket)

Diese Funktion prüft lediglich, ob sich ein Datagramm in der Eingangs-Queue des Netzwerksystems befindet. Sie kehrt mit ihrem Ergebnis sofort zurück.

VAR
  U : U_LONG;
begin
  IoctlSocket (Sock, FIONREAD, @U);
  IF U > 0
    THEN MmoLog.Lines.Add ('Next Datagram is '+IntToStr (U)+' Bytes long')
    ELSE MmoLog.Lines.Add ('No Datagram waiting at this moment');

Paket empfangen (RecvFrom)

Die RecvFrom-Funktion ruft das nächste Datagramm aus der Eingangs-Queue des Netzwerk-Subsystems ab. Sollte sich dort kein Datagramm befinden, wartet ("blockiert") RecvFrom so lange, bis eines eintrifft.

VAR
  Buffer   : ARRAY [0..512] OF CHAR;
  SockAddr : TSockAddr_In;
  SAL      : INTEGER;
  I        : INTEGER;
BEGIN
  FillChar (SockAddr, SizeOf (SockAddr), 0);
  SAL := SizeOf (SockAddr);

  I := RecvFrom (Sock, @Buffer, 512, 0, @SockAddr, @SAL); 
       // 512 = Maximale Anzahl zu empfangender Bytes
       // 0   = Keine OOB-Daten lesen, kein Peek
       // @SockAddr = Hier werden die Informationen über den Peer abgelegt
       //             SockAddr.sin_Addr = IP-Adresse des Peer
       //             SockAddr.sin_Port = Port-Nummer beim Peer
       // @SAL      = Länge der zurückgegebenen SockAddr-Struktur
       // I  (RGW)  = Länge des Pakets in Bytes, wenn positiv
  IF I = SOCKET_ERROR THEN EXIT;

Antwort versenden (SendTo)

Mit SendTo werden Datagramme versandt. Da hier auf ein bei RecvFrom eingegangenes Datagramm geantwortet wird, können die bei RecvFrom eingegangenen Daten "SockAddr" und "SAL" gleich wiederverwendet werden.

  SendTo (Sock, @Paket, PaketLaenge, 0, @SockAddr, SAL);
      // @Paket      = Zeiger auf zurück zu sendenden Byte-Block
      // PaketLaenge = Länge des Byte-Blocks
      // 0           = Routing erlaubt; kein OOB-Versand
      // @SockAddr   = Von RecvFrom zurück erhaltene SockAddr-Struktur
      // SAL         = Von RecvFrom zurück erhaltene Länge der SockAddr-Struktur

Listener schließen (CloseSocket)

  CloseSocket (Sock);

Client

Ein Client sendet lediglich Pakete an eine bestimmte IP-Adresse und einen Port. Er kann dann die zurückkommenden Pakete wieder empfangen. Eine "Verbindung" wird nicht aufgebaut, da UDP verbindungslos ist.

Ablauf:

Ermittlung der IP-Adresse des Peer (Inet_Addr)

Umwandlung einer IP-Adresse in Dotted Decimal Notation (z. B. 192.168.1.2) in die 4-Byte-Darstellung vom Typ TIn_Addr. INADDR_BROADCAST ist ein vordefinierter Wert für die Broadcast-Adresse 255.255.255.255.

VAR
  IpV4Addr : TIn_Addr;
BEGIN
  IF DoBroadcast 
    THEN IpV4Addr.S_Addr := INADDR_BROADCAST
    ELSE IpV4Addr.S_addr := Inet_Addr (PChar (IpAddrStr));

Socket erzeugen (Socket)

VAR
  SAI      : TSockAddr_In;
  SAL      : INTEGER;
  SockAddr : pSockAddr;
begin
  FillChar (SAI, SizeOf (SAI), 0);
  SAI.sin_family := AF_INET;         // Internet-Adress-Familie
  SAI.sin_port   := HtoNs (PortNo);  // Port-Nummer
  SAI.sin_addr   := IpV4Addr;        // IP-Adresse, wie vorher ermittelt
  SockAddr       := @SAI;
  Sock           := Socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);     // Socket erzeugen

Socket für Broadcast freischalten (SetSockOpt)

Hiermit wird die Socket-Option "Broadcast" auf TRUE geschaltet. Erst dann wird die Empfänger-Adresse 255.255.255.255 als Broadcast-Adresse interpretiert und nicht als illegale Adresse. Dies ist nur für Broadcasts erforderlich.

VAR
  B : BOOL;
BEGIN
  B := TRUE;
  SetSockOpt (Sock, SOL_SOCKET, SO_BROADCAST, @B, SizeOf (B));

Daten senden (SendTo)

Absenden eines Datagramms mit SendTo. Vor allem bei Broadcasts sollte mit der Option MSG_DONTROUTE dafür gesorgt werden, dass das Datagramm nicht in andere Netzwerke geroutet wird.

  SendTo (Sock, @DataBuf, DataBufLength, MSG_DONTROUTE, SockAddr, SizeOf (SAI));
      // @DataBuf      = Zeiger auf Daten-Puffer
      // DataBufLength = Anzahl Bytes im Daten-Puffer
      // MSG_DONTROUTE = Paket nicht durch Router lassen
      // SockAddr      = SockAddr-Struktur (mit Peer-Adresse und Port-Nummer)
      // SizeOf (SAI)  = Länge der SockAddr-Struktur

Antwort empfangen (RecvFrom)

Analog zum Listener wird hier die Antwort von dem Host empfangen, an den der letzte SendTo ging. Daher kann/muss die dort gefüllte Datenstruktur SockAddr wiederverwendet werden.

VAR
  DataBuf  : ARRAY [0..512] OF CHAR;
  I        : INTEGER;
BEGIN
  SAL := SizeOf (SAI);
  I := RecvFrom (Sock, @DataBuf, 512, 0, SockAddr, @SAL);
  IF I > 0 THEN BEGIN
    // Daten in DataBuf verarbeiten
    END;
  END;

Socket schließen (CloseSocket)

  CloseSocket (Sock);

Stefan Heymann