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;
}