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:
Die wichtigsten Stellen werden im Folgenden besprochen, auf der Seite Beispiel-Programme sind die vollständigen Programme zu finden.
Dieser Vorgang wurde in Tour 2 ausführlich besprochen.
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.
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.
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.
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>