Es meldet, dass es bis fertig ist Rückkehr.
select
wartet auf Ereignisse, die normalerweise außerhalb der Kontrolle Ihres Programms liegen. Im Wesentlichen durch Aufruf von select
, sagt Ihr Programm "Ich habe nichts zu tun bis ..., bitte unterbrechen Sie meinen Prozess".
Die von Ihnen angegebene Bedingung ist eine Reihe von Ereignissen, von denen Sie jedes aufwecken wird.
Wenn Sie beispielsweise etwas herunterladen, müsste Ihre Schleife warten, bis neue Daten eintreffen, ein Timeout eintritt, wenn die Übertragung hängen bleibt, oder der Benutzer unterbricht, was genau select
ist tut.
Wenn Sie mehrere Downloads haben, lösen Daten, die auf einer der Verbindungen ankommen, Aktivitäten in Ihrem Programm aus (Sie müssen die Daten auf die Festplatte schreiben), also geben Sie eine Liste aller Download-Verbindungen an select
in der Liste der Dateideskriptoren, die auf "read" zu achten sind.
Wenn Sie gleichzeitig Daten irgendwohin hochladen, verwenden Sie wieder select
um zu sehen, ob die Verbindung derzeit Daten akzeptiert. Wenn sich die andere Seite im Wählmodus befindet, werden Daten nur langsam bestätigt, sodass Ihr lokaler Sendepuffer immer voll ist und jeder Versuch, weitere Daten zu schreiben, blockiert, bis der Pufferspeicher verfügbar ist, oder fehlschlägt. Indem wir den Dateideskriptor übergeben, senden wir an select
als "write"-Deskriptor werden wir benachrichtigt, sobald Pufferplatz zum Senden verfügbar ist.
Die allgemeine Idee ist, dass Ihr Programm ereignisgesteuert wird , d. h. es reagiert auf externe Ereignisse aus einer gemeinsamen Nachrichtenschleife, anstatt sequentielle Operationen auszuführen. Sie sagen dem Kernel "das ist die Menge von Ereignissen, für die ich etwas tun möchte", und der Kernel gibt Ihnen eine Reihe von Ereignissen, die aufgetreten sind. Es ist ziemlich üblich, dass zwei Ereignisse gleichzeitig auftreten; B. eine TCP-Bestätigung in ein Datenpaket aufgenommen wurde, kann dies dasselbe fd sowohl lesbar (Daten sind verfügbar) als auch beschreibbar machen (bestätigte Daten wurden aus dem Sendepuffer entfernt), sodass Sie darauf vorbereitet sein sollten, alle Ereignisse zu verarbeiten bevor Sie select
aufrufen wieder.
Einer der Feinheiten ist dieser select
gibt Ihnen im Grunde ein Versprechen, dass ein Aufruf von read
oder write
wird nicht blockieren, ohne eine Garantie für den Anruf selbst zu übernehmen. Wenn zum Beispiel ein Byte Pufferplatz verfügbar ist, können Sie versuchen, 10 Bytes zu schreiben, und der Kernel wird zurückkommen und sagen:"Ich habe 1 Byte geschrieben", also sollten Sie darauf vorbereitet sein, auch diesen Fall zu handhaben. Ein typischer Ansatz besteht darin, einen Puffer "Daten, die in diesen fd geschrieben werden sollen" zu haben, und solange er nicht leer ist, wird der fd dem Schreibsatz hinzugefügt, und das "schreibbare" Ereignis wird behandelt, indem versucht wird, alles zu schreiben die aktuell im Puffer befindlichen Daten. Wenn der Puffer danach leer ist, gut, wenn nicht, einfach wieder auf "schreibbar" warten.
Das „Ausnahme“-Set wird selten verwendet – es wird für Protokolle verwendet, die Out-of-Band-Daten haben, bei denen es möglich ist, dass die Datenübertragung blockiert wird, während andere Daten durchgehen müssen. Wenn Ihr Programm derzeit keine Daten von einem "lesbaren" Dateideskriptor akzeptieren kann (z. B. wenn Sie herunterladen und die Festplatte voll ist), möchten Sie den Deskriptor nicht in den "lesbaren" Satz aufnehmen, da Sie das Ereignis nicht verarbeiten können und select
würde bei einem erneuten Aufruf sofort zurückkehren. Wenn der Empfänger den fd in den „Ausnahme“-Satz aufnimmt und der Absender seinen IP-Stack auffordert, ein Paket mit „dringenden“ Daten zu senden, wird der Empfänger aufgeweckt und kann entscheiden, die unverarbeiteten Daten zu verwerfen und mit dem Absender neu zu synchronisieren . Die telnet
Das Protokoll verwendet dies beispielsweise für die Verarbeitung von Strg-C. Sofern Sie kein Protokoll entwerfen, das eine solche Funktion erfordert, können Sie dies problemlos weglassen, ohne Schaden zu nehmen.
Obligatorisches Codebeispiel:
#include <sys/types.h>
#include <sys/select.h>
#include <unistd.h>
#include <stdbool.h>
static inline int max(int lhs, int rhs) {
if(lhs > rhs)
return lhs;
else
return rhs;
}
void copy(int from, int to) {
char buffer[10];
int readp = 0;
int writep = 0;
bool eof = false;
for(;;) {
fd_set readfds, writefds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
int ravail, wavail;
if(readp < writep) {
ravail = writep - readp - 1;
wavail = sizeof buffer - writep;
}
else {
ravail = sizeof buffer - readp;
wavail = readp - writep;
}
if(!eof && ravail)
FD_SET(from, &readfds);
if(wavail)
FD_SET(to, &writefds);
else if(eof)
break;
int rc = select(max(from,to)+1, &readfds, &writefds, NULL, NULL);
if(rc == -1)
break;
if(FD_ISSET(from, &readfds))
{
ssize_t nread = read(from, &buffer[readp], ravail);
if(nread < 1)
eof = true;
readp = readp + nread;
}
if(FD_ISSET(to, &writefds))
{
ssize_t nwritten = write(to, &buffer[writep], wavail);
if(nwritten < 1)
break;
writep = writep + nwritten;
}
if(readp == sizeof buffer && writep != 0)
readp = 0;
if(writep == sizeof buffer)
writep = 0;
}
}
Wir versuchen zu lesen, wenn wir Pufferplatz verfügbar haben und auf der Leseseite kein Dateiende oder Fehler aufgetreten ist, und wir versuchen zu schreiben, wenn wir Daten im Puffer haben; wenn das Dateiende erreicht ist und der Puffer leer ist, dann sind wir fertig.
Dieser Code verhält sich eindeutig suboptimal (es ist Beispielcode), aber Sie sollten sehen können, dass es für den Kernel akzeptabel ist, sowohl beim Lesen als auch beim Schreiben weniger zu tun, als wir verlangt haben. In diesem Fall gehen wir einfach zurück und sagen "wann immer Sie sind bereit", und dass wir niemals lesen oder schreiben, ohne zu fragen, ob es blockiert wird.
Von derselben Manpage:
Beim Beenden werden die Sätze an Ort und Stelle modifiziert, um anzuzeigen, welche Dateideskriptoren tatsächlich ihren Status geändert haben.
Verwenden Sie also FD_ISSET()
auf den zur Auswahl übergebenen Sätzen, um zu bestimmen, welche FDs fertig geworden sind.