2016-08-03 9 views
0

Ich mache einen blockierenden connect() -Aufruf auf einem Client-UNIX-Socket. Unten ist ein Beispiel für den Code:linux: Warum blockiert connect(), wenn der Annahmeruf fehlgeschlagen ist?

// Create socket. 

    fds[i] = socket(AF_UNIX, SOCK_STREAM, 0); 
    if (fds[i] == -1) 
     { 
     result = -1; 
     goto done; 
     } 
    printf("generate_load thread, fds[%d]: %d\n", i, fds[i]); 
//  int flags = fcntl(fds[i], F_GETFL); 
//  fcntl(fds[i], F_SETFL, flags | O_NONBLOCK); 

    // If we have a timeout value we're only going to use that as 
    // a connect timeout. From looking at some source code, it 
    // appears the only way to timeout (correctly) a unix domain 
    // socket connect() call is to set the send timeout. 

    struct timeval existing_timeout; 
    if (timeout != 0) 
     { 
     socklen_t len = sizeof(existing_timeout); 
     getsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &existing_timeout, 
       &len); 

     struct timeval tv; 
     tv.tv_sec = timeout/1000000; 
     tv.tv_usec = timeout % 1000000; 
     setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); 
     } 

    // Set socket name. 

    memset(&addr, 0, sizeof(addr)); 
    addr.sun_family = AF_UNIX; 
    strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1); 

    // @ indicates abstract name and abstract names begin with a NULL 
    // byte. 

    if (socket_name[0] == '@') 
     addr.sun_path[0] = '\0'; 

    // Connect. 

    result = connect(fds[i], (struct sockaddr*) &addr, sizeof(addr)); 
    if (result == -1) 
     { 
     printf("generate_load thread, failed connecting: %d\n", errno); 
     if (errno == EAGAIN) 
      errno = ETIMEDOUT; 
     goto done; 
     } 

    printf("generate_load thread, connected fds[%d]: %d\n", i, fds[i]); 

    // If we set a timeout then set it back to what it was. 

    if (timeout != 0) 
     { 
     setsockopt(fds[i], SOL_SOCKET, SO_SNDTIMEO, &existing_timeout, 
       sizeof(existing_timeout)); 
     } 

Dieser Code alle bis zur Annahme Seite funktionieren gut, die jetzt im gleichen Prozess ist, an den Grenzwert für Dateideskriptoren nicht fällig. Der accept() -Aufruf schlägt fehl mit errno = 24 (EMFILE). Mir geht es gut mit dem Fehler, aber warum sieht der Client keinen Fehler? Stattdessen wird der Client blockiert und kehrt nie zurück. Wie Sie sehen können, habe ich die Zeilen auskommentiert, die den Socket in den nicht blockierenden Modus bringen. Ich glaube im nicht-blockierenden Modus finde ich einige EAGAIN-Fehler.

Auch wenn ich die Datei-Deskriptor-Grenze traf, scheint die akzeptierende Seite ständig versucht, diesen Socket zu akzeptieren. Ich verwende Select() und warte darauf, dass der Abhörsocket bereit zum Lesen ist. Wenn es ist, tue ich ein accept(). Ich kann verstehen, den ersten EMFILE-Fehler zu bekommen, aber ich hätte gedacht, dass der Fehler zurück zum Aufruf connect() übertragen worden wäre, was dazu geführt hätte, dass der Code aus seiner Schleife ausbricht und somit keine Verbindungsaufrufe mehr getätigt werden Ich hätte gedacht, dass die akzeptierende Seite beim Aufruf von select() blockiert würde.

Unten ist ein Ausschnitt der Zuhörseite. Der folgende Code ist innerhalb einer while (1) Schleife, die ersten Anrufe auswählen():

if (FD_ISSET(ti->listen_fd, &read_set) != 0) 
    { 
    printf("select thread, accepting socket\n"); 
    int sock = accept(ti->listen_fd, NULL, NULL); 
    printf("select thread, accepted socket\n"); 
    if (sock == -1) 
     { 
     printf("select thread, failed accepting socket: %d\n", errno); 
     if (error_threshold_met(&eti) == 0) 
      { 
      log_event(LOG_LEVEL_ERROR, "select thread, accept() " 
        "failed: %s", get_error_string(errno, error_string, 
        sizeof(error_string))); 
      } 
     } 

Der Code scheint gut zu funktionieren, bis ich die Dateideskriptor Grenze 1024 getroffen. Irgendwelche Ideen, warum es sich so verhält? Sollte es sein und ich verstehe einfach nicht, wie es funktionieren soll?

Danke, Nick

+2

Während auf dem Server die Dateizugriffsnummer nicht mehr vorhanden war, hat das Betriebssystem möglicherweise den Clientverbindungsversuch in die Warteschlange gestellt. Der Client trennt die Verbindung nur, wenn die Verbindung verweigert wird. Das Fehlen einer Antwort blockiert den Client. – alvits

+0

@alvits: Danke. Also gibt es keine Möglichkeit für mich, dieses Problem zu lösen? Die Verbindung() wird für unbegrenzte Zeit blockiert? Ist die einzige Lösung, um den Aufruf von connect() zu beenden? – nickdu

+0

@nickdu - Ich sehe, dass Sie bereits einen Timeout-Wert senden, sollten Sie den Socket auf "O_NONBLOCK" setzen. – alvits

Antwort

3

connect() und accept() sind nicht verriegelt. Sie können connect() anrufen und es zurückgeben, ohne überhaupt accept() zu rufen. Der serverseitige Teil des TCP-Handshakes erfolgt unabhängig von accept() im Kernel. Alles, was accept() tut, ist eine eingehende Verbindung aus einer Warteschlange auszuwählen und einen Socket darum herum zu erstellen, der blockiert, während die Warteschlange leer ist. Der Socket-Erstellungsteil fällt wegen FD-Erschöpfung aus, aber die tatsächliche Verbindung ist bereits hergestellt.

+0

Danke, aber ich bin mir nicht sicher, ob das stimmt. Wenn ja, warum wird mein connect() - Anruf blockiert, wenn ich diese Bedingung treffe? Außerdem habe ich den Rückstand auf den listen() Anruf auf Null gesetzt. – nickdu

+0

Es ist wahr in Ordnung. Die Wartestatuswarteliste füllt sich, wenn Sie nicht akzeptieren, was schließlich dazu führt, dass neue Verbindungen blockieren oder fehlschlagen, abhängig von der Serverplattform. – EJP

+0

Ich glaube nicht, dass der Kernel die Verbindung bestätigt (z. B. ein SYN-Paket sendet), bis accept() zurückkehrt. – Max