Connection-Timeout
Um zu verhindern, dass ein Programm ewig auf eine Verbindung wartet, kann ein Timeout eingebaut werden. Ein bestehender connect-Aufruf kann einfach durch diese Funktion ersetzt werden.
Funktion
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/socket.h>
int timed_connect(
int sockno,
struct sockaddr *addr,
size_t addrlen,
struct timeval *timeout)
{
...
Parameter
Die Parameter entsprechen denen von connect. Zusätzlich wird einfach noch ein Timeout-Parameter übergeben.
sockno
Das Socket, das verbunden werden soll.
addr
Die Adresse, mit dem das Socket verbunden werden soll.
addrlen
Die Länge der Adress-Resource.
timeout
Die maximale Wartezeit auf eine erfolgreiche Verbindung.
Rückgabewert
Die Funktion gibt 0 zurück wenn alles geklappt hat oder -1 falls ein Fehler aufgetreten ist. Ist das Timeout erreicht, wird -1 zurückgegeben und errno auf ETIMEDOUT
gesetzt.
Warten
Das Socket wird zu Beginn mit O_NONBLOCK
auf «nicht-blockierend» gesetzt. So kehrt connect sofort zurück ohne zu blockieren. Die Flags des Sockets werden am Schluss dieses Abschnitts wieder auf die ursprünglichen Werte gesetzt.
Nachdem connect
aufgerufen wurde muss errno
den Wert EINPROGRESS
haben, was soviel bedeutet wie, dass der Connect-Vorgang jetzt am laufen ist. Ist dies nicht der Fall, ist ein anderer Fehler aufgetreten und res
hat den Wert -1. Wenn connect sofort wieder zurückkehrt (z.B. bei lokalen Verbindungen) gibt es den Wert 0 zurück und das Warten auf die Verbindung ist nicht nötig.
Mit select
wird jetzt die Beschreibbarkeit des Sockets geprüft. Wenn die Verbindung erfolgreich war kehrt select
mit einem Wert grösser 0 zurück. Wenn das Timeout abgelaufen ist gibt select
den Wert 0 zurück.
...
int res, opt;
if ((opt = fcntl(sockno, F_GETFL, NULL)) < 0)
return -1;
if (fcntl(sockno, F_SETFL, opt | O_NONBLOCK) < 0)
return -1;
if ((res = connect(sockno, addr, addrlen)) < 0) {
if (errno == EINPROGRESS) {
fd_set wait_set;
FD_ZERO(&wait_set);
FD_SET(sockno, &wait_set);
res = select(sockno + 1, NULL, &wait_set, NULL, timeout);
}
} else {
res = 1;
}
if (fcntl(sockno, F_SETFL, opt) < 0)
return -1;
...
Fehlerprüfung
Wenn res
von connect
oder select auf -1 gesetzt wurde signalisiert das einen Fehler und die Funktion ist fehlgeschlagen. Die Fehlernummer kann mit errno
abgerufen werden.
res
hat den Wert 0, falls das Timeout von select
abgelaufen ist. Die Fehlernummer wird hier manuell auf ETIMEDOUT
gesetzt.
Im letzten Fall sollte res
den Wert 1 haben und connect und select
sind Fehlerfrei zurückgekehrt. Jedoch kann immer noch nicht mit Sicherheit gesagt werden, ob die Verbindung wirklich geklappt hat, da connect
bei bestimmten Netzwerk-Fehlern trotzdem mit 0 zurückkehrt. Um späte Überraschungen zu vermeiden wird mit getsockopt
auf Socket-Ebene die Fehlernummer abgerufen. ist diese 0, lief die Verbindung erfolgreich.
...
if (res < 0) {
return -1;
} else if (res == 0) {
errno = ETIMEDOUT;
return -1;
} else {
socklen_t len = sizeof(opt);
if (getsockopt(sockno, SOL_SOCKET, SO_ERROR, &opt, &len) < 0)
return -1;
if (opt) {
errno = opt;
return -1;
}
}
return 0;
}