11.20 Nicht blockierende I/O-Sockets
Alle Socket-Beispiele, die Sie bisher kennen gelernt haben, haben die Ausführung des Programms blockiert (warten), wenn ein Socket-Aufruf seine Aktion nicht ausführen konnte. Das Blockieren von Sockets ist die Standardeinstellung – dies ist aber nicht unbedingt nötig. Sie können z. B. fcntl() verwenden, um einen Socket auf nicht blockierend zu setzen. Aber einen gravierenden Nachteil von nicht blockierenden Sockets gleich am Anfang: Das Programm befindet sich dadurch immer in einem dauerhaften Poll-Zustand, das heißt, es wird dauernd nach Daten am Socket überprüft, und das kostet CPU-Zeit, die bei einem Server kostbar ist. Nicht blockierende Sockets lassen sich in mehrere Kategorien einteilen:
|
Eingehende Verbindungen mit accept() – Wird accept() für einen blockierenden Socket aufgerufen und es liegt keine Verbindung vor, wartet der Prozess so lange, bis eine Verbindung vorliegt. Das ist die Standardeinstellung. Wird accept() hingegen von einem nicht blockierenden Socket aufgerufen und es liegt keine Verbindung an, wird der Fehler EWOULDBLOCK (= EAGAIN) zurückgegeben. Entsprechend müssen Sie diese Fehlermeldung auch mit errno bearbeiten – sprich: abfangen. |
|
Ausgehende Verbindungen mit connect() – Richtet ein Client ein connect() an den Server (bei TCP) und es handelt sich um einen blockierenden Socket, so wartet connect() mindestens eine Zeitspanne von RTT (Round-Trip-Time) auf die Antwort vom Server. Wird connect() hingegen mit einem nicht blockierenden Socket aufgerufen und die Verbindung zur Gegenstelle wird nicht aufgebaut, liefert connect() den Fehler EINPROGRESS zurück. Auch diesen Fehler müssen Sie dann für den Fall der Fälle behandeln. |
|
Ein- und Ausgabeoperationen mit read(), recv(), recvfrom() bzw. write(), send(), sendto() – Bei Eingabeoperationen wird in der Standardeinstellung das Socket so lange blokkiert, bis Daten an diesem gelesen oder gesendet werden konnten. Bei TCP (als stream-orientierte Datenübertragung) wird die Blockierung nach Eintreffen von Daten aufgehoben. Bei UDP hingegen wird die Blockierung erst aufgehoben, wenn ein ganzes Paket (UDP-Datagramm) gesendet wird. Dies ist die Standardeinstellung. Werden Eingabeoperationen auf einem nicht blockierenden Socket ausgeführt, obwohl keine Eingaben vorhanden sind, wird der Fehler EWOULDBLOCK zurückgegeben. Bei den Ausgabeoperationen werden die Sockets blockiert, wenn für den Sendepuffer des blockierenden Sockets nicht genügend Platz reserviert wurden. Ist bei einem nicht blockierenden Socket nicht genügend Speicherplatz für den Sendepuffer vorhanden, wird EWOULDBLOCK zurückgegeben. |
Im folgenden Beispiel sehen Sie eine einfache Möglichkeit von nicht blockierenden Sockets. Im Beispiel wird accept() eines TCP-Servers, der auf eine Verbindung eines Client wartet, auf nicht blockierend eingestellt. Nach jedem accept()-Aufruf fährt das Programm fort. In diesem Beispiel wird überprüft, ob accept() erfolgreich war oder nicht. Bei negativer Abfrage wird einfach die Meldung Poll: Kein Client am Socket ... jede Sekunde ausgegeben. Ebenso könnten Sie vorgehen, wenn Sie das zurückgegebene Socket von accept(), das anschließend für Lese- und Schreiboperationen verwendet wird, auf nicht blockierend setzen wollen – was im Beispiel in der do-while()-Schleife demonstriert wurde. Bei dem Listing handelt es sich allerdings nur um ein Grundgerüst, das aber den Sachverhalt der nicht blockierenden Ein-/Ausgabe von Sockets recht gut demonstriert.
/* non_block.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd. h.>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#define BUF 1024
int main (void) {
int create_socket, new_socket, addrlen;
char *buffer = malloc (BUF);
struct sockaddr_in address;
long save_fd;
const int y = 1;
printf ("\e[2J");
if ((create_socket=socket( AF_INET, SOCK_STREAM, 0)) > 0)
printf ("Socket wurde angelegt\n");
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons (15000);
setsockopt( create_socket, SOL_SOCKET,
SO_REUSEADDR, &y, sizeof(int) );
if (bind( create_socket,
(struct sockaddr *) &address,
sizeof (address)) == 0 )
printf ("Binding Socket\n");
listen (create_socket, 5);
addrlen = sizeof (struct sockaddr_in);
save_fd = fcntl( create_socket, F_GETFL );
save_fd |= O_NONBLOCK;
fcntl( create_socket, F_SETFL, save_fd );
while (1) {
new_socket = accept ( create_socket,
(struct sockaddr *) &address,
&addrlen );
if (new_socket > 0)
printf ("Der Client %s ist verbunden ...\n",
inet_ntoa (address.sin_addr));
else {
printf("Poll: Kein Client am Socket ...\n");
sleep(1);
continue; /* wieder zum Schleifenanfang */
}
do {
/* Socket für die Datenübertragung auf NON-Blocking */
save_fd = fcntl( new_socket, F_GETFL );
save_fd |= O_NONBLOCK;
fcntl( new_socket, F_SETFL, save_fd );
/* Daten verarbeiten */
} while (strcmp (buffer, "quit\n") != 0);
close (new_socket);
}
close (create_socket);
return 0;
}
Das Programm bei der Ausführung:
$ gcc -o non_block non_block.c
$ ./non_block
Socket wurde angelegt
Binding Socket
Poll: Kein Client am Socket ...
Poll: Kein Client am Socket ...
Poll: Kein Client am Socket ...
Poll: Kein Client am Socket ...
Poll: Kein Client am Socket ...
...
Um also ein Socket (hier durch Socket-Deskriptor) auf nicht blockierend zu setzen, können Sie immer den folgenden Codeausschnitt einsetzen:
save_fd = fcntl( create_socket, F_GETFL );
save_fd |= O_NONBLOCK;
fcntl( create_socket, F_SETFL, save_fd );
Mehr zu fcntl() können Sie dem Kapitel 2 in diesem Buch entnehmen.
|