Tour 4: "udp-echo"

Überblick

Ein weiterer einfacher Dienst ist der echo-Service. Dieser ist in RFC 862 sowohl für TCP als auch für UDP definiert. Da wir mit TCP schon Erfahrung gesammelt haben, wollen wir den Dienst für UDP implementieren.

Obwohl die Manpage und Beispiel-Konfiguration für inetutils-inetd unter Ubuntu es nicht vermuten lassen, so ist doch ein UDP-echo-Service enthalten. Man aktiviert ihn durch folgenden Eintrag in /etc/inetd.conf:

echo            dgram   udp     wait    root    internal

In unserem Programm müssen wir die folgenden Aufgaben lösen:

  1. Den auf der Kommandozeile übergebenen Hostnamen in eine IP-Adresse auflösen
  2. Einen UDP-Socket erzeugen
  3. Eine Zeichenkette von der Konsole einlesen
  4. Ein Datagramm an Port 7 senden
  5. Das empfangene Datagramm ausgeben

Die wichtigsten Stellen werden im Folgenden besprochen, auf der Seite Beispiel-Programme sind die vollständigen Programme zu finden.

Den Hostnamen auflösen

Dieser Vorgang wurde in Tour 2 ausführlich besprochen.

Einen UDP-Socket erzeugen

Ein UDP-Socket wird genauso wie ein TCP-Socket erzeugt, mit dem einzigen Unterschied, dass SOCK_DGRAM statt SOCK_STREAM angegeben wird:

int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1)
{
        fprintf(stderr, "Couldn't create socket: %s\n", strerror(errno));
}

Mit der 0 lassen wir dem System die Wahl, welches Protokoll eingesetzt werden soll. Trotzdem bekommen wir bei SOCK_DGRAM für gewöhnlich einen UDP-Socket.

Datagramme senden

Auf einem UDP-Socket können Datagramme auch dann versendet werden, wenn dieser nicht mit einem Ziel verbunden ist. Dazu wird der Befehl sendto() verwendet, der als Parameter eine Zieladresse erwartet:

struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr = addr;
int bytes = sendto(sock, str, strlen(str), 0, (struct sockaddr*) &saddr, sizeof(saddr));
if (bytes == -1)
{
        fprintf(stderr, "Couldn't write to socket: %s\n", strerror(errno));
}

Trotzdem ist es möglich, mit connect() eine bestimmte Zieladresse zuzuweisen. Auf solch einem verbundenen UDP-Socket können die Befehle send() zum Senden an die festgelegte Zieladresse und sendto() zum Senden an beliebige Adressen benutzt werden.

Datagramme empfangen

Zum Empfangen auf UDP-Sockets können sowohl recv() als auch recvfrom() benutzt werden. Der Unterschied ist, dass bei recvfrom() die Absenderadresse aufgefangen wird:

char buffer[BUFFER_SIZE];
struct sockaddr_in saddr;
socklen_t size = sizeof(saddr);
int bytes = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*) &saddr, &size);
if (bytes >= 0)
{
        printf("Received %i bytes from %s:%u.\n", bytes, inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
}
else
{
        fprintf(stderr, "Couldn't read from socket: %s\n", strerror(errno));
}

Hier gilt wie bei accept(), dass der Parameter für die Adress-Größe beim Aufruf bereits vorbelegt sein muss.

Ausgabe des fertigen Programms

Unter Linux mit dem in inetutils-inetd eingebauten echo-Server entsteht folgende Ausgabe:

$ ./echo localhost
Resolving hostname "localhost"... OK.
> blabla
Sent 6 bytes to 127.0.0.1:7.
Received 6 bytes from 127.0.0.1:7: <blabla>

Zurück