Das UDP ist ein
unzuverlässiges [unreliable]
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.
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.
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.
Ein UDP-Listener wird folgendermaßen aufgebaut:
Ggf. Antwort-Paket senden mit der SendTo-Funktion
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;
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));
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');
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;
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
CloseSocket (Sock);
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:
Zerstören der Socket-Instanz mit CloseSocket.
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));
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
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));
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
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;
CloseSocket (Sock);