Select
Um zu verhindern, dass ein Programm ewig auf eine Verbindung wartet, kann ein Timeout eingebaut werden. Wer select
versteht, kann sich gar nicht mehr vorstellen jemals ohne damit ausgekommen zu sein. Dabei ist das Grundkonzept ziemlich simpel: Überwache mehrere File-Descriptoren, warte bis etwas passiert und gib zurück, wo etwas passiert ist. Sie ist also eine Art Multiplexer für File-Descriptoren.
File-Descriptor-Sets
Ein Datentyp der unverzichtbar ist um die Funktion zu verstehen und darum einer genaueren Erläuterung bedarf, ist fd_set
. Er kann mit File-Descriptoren gefüllt werden, die der Prozess geöffnet hat und die schliesslich von select
überwacht werden sollen.
fd_set read_fds;
Zum Befüllen stehen vier Makros zur Verfügung. Wichtig ist, dass vor der ersten Verwendung die Variable immer zuerst mit FD_ZERO
geleert werden muss! Ansonsten geistern im Set zufällige File-Descriptoren umher.
FD_ZERO(&read_fds); // Leert das Set
FD_SET(STDIN_FILENO, &read_fds); // Setzt einen File-Descriptor
FD_CLR(STDIN_FILENO, &read_fds); // Entfernt einen File-Descriptor
FD_ISSET(STDIN_FILENO, &read_fds); // Prüft, ob ein File-Descriptor gesetzt ist
Die Funktion
Jetzt aber zur Funktionsbeschreibung. Die Funktion benötig fünf Parameter, wobei einige selten bis gar nicht benutzt werden. Allen Set-Parametern kann auch NULL
übergeben werden, falls sie nicht benötigt werden.
Der Inhalt der Sets ist aber nach dem Aufruf nicht mehr gleich wie zuvor! select` setzt darin nämlich die zur Verfügung stehenden File-Descriptoren. Das heisst die ursprünglich Sets müssen vor jedem Aufruf wieder neu gesetzt werden.
Ebenso wird der Timeout-Parameter unter Umständen verändert. Es ist jedoch implementierungsabhängig, welcher Wert er nach dem Aufruf enthält. Auf jeden Fall muss auch er vor jedem Aufruf auf den Ursprungswert zurückgesetzt werden.
int select(
int nfds,
fd_set *read_fds,
fd_set *write_fds,
fd_set *except_fds,
struct timeval *timeout
);
Parameter
nfds
Der erste Parameter muss den im Moment grössten, geöffneten File-Descriptor + 1 enthalten. Die Eigenheit, dass dem Wert immer eins dazu gezählt werden muss hat sich inzwischen eingebürgert. Sie hat jedoch keinen logischen Hintergrund und ist wohl auf die ursprüngliche Implementierung zurückzuführen. Es ist eine schlechte Idee, den gerade grössten File-Descriptor durch Spekulation herausfinden zu wollen. Daher sollte man sich während des Programmablaufes laufend den gerade grössten File-Descriptor irgendwo merken.
read_fds
Dieses Set enthält alle File-Descriptoren, die auf Lesbarkeit überprüft werden sollen. Da heisst sobald Daten am File-Descriptor vorhanden sind. write_fds
In diesem Set sind alle File-Descriptoren, die auf Beschreibbarkeit geprüft werden sollen (z.B. bei Pipes); also wenn Daten geschrieben werden können.
except_fds
Dieses Set wird nicht zum Abfangen von Fehlern verwendet, wie der Name vielleicht vermuten lässt. Es wird eingesetzt, um auf so genannte «Out-of-band»-Daten bei Sockets zu reagieren.
timeout
Der Timeout-Parameter gibt an, wie lange maximal auf ein Ereignis gewartet werden soll. Danach kehrt select mit dem Wert 0 zurück. Wird NULL
übergeben wartet select unendlich lange.
Rückgabewert
Der Rückgabewert von select
kann folgende Werte annehmen: Den Wert -1 falls ein Fehler aufgetreten ist, oder select
durch ein Signal unterbrochen worden ist. Den Wert 0 wenn das Timeout abgelaufen ist. Einen Wert > 0 gibt die Anzahl zur Verfügung stehenden File-Descriptoren an. Lasst die Suche beginnen!
Jetzt, wo man weiss wie viele File-Descriptoren zur Verfügung stehen, wie findet man da heraus welche es sind? Durch Suchen! Es gibt keine Abkürzung für diesen Schritt. Man muss diejenigen Sets durchkämmen, in denen man die File-Descriptoren vermutet.
Beispiel
Ein ganz simples Beispiel, dass auf eine Eingabe aus der Kommandozeile wartet und die ensprechende Zeile wiederholt ausgibt. Falls 5 Sekunden lang nichts passiert, wird «Timeout!» ausgegeben.
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
#define BUFFER_SIZE 1024
int main(int argc, const char *argv[])
{
fd_set read_fds0;
FD_ZERO(&read_fds0);
FD_SET(STDIN_FILENO, &read_fds0);
do {
int res;
fd_set read_fds;
struct timeval timeout;
char buffer[BUFFER_SIZE];
ssize_t length;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
read_fds = read_fds0;
if ((res = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout)) < 0) {
perror("select");
return 1;
} else if (res == 0) {
printf("Timeout!\n");
} else {
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
length = read(STDIN_FILENO, buffer, BUFFER_SIZE);
printf("%d Zeichen geschrieben\n", length);
write(STDIN_FILENO, buffer, length);
}
}
} while (1);
return 0;
}