Wie verwende ich setsockopt(SO_REUSEADDR)?

Lesezeit: 5 Minuten

Benutzeravatar von user3735849
Benutzer3735849

Ich betreibe meinen eigenen HTTP-Server auf einem Himbeer-Pi. Das Problem ist, wenn ich das Programm stoppe und neu starte, ist der Port nicht mehr verfügbar. Manchmal bekomme ich das gleiche Problem, wenn ich viele Anfragen erhalte.
Ich möchte SO_REUSEADDR verwenden, damit ich den Port auch dann weiter verwenden kann, wenn der Fehler auftritt, aber kein Glück hatte, ihn einzurichten. Unten ist mein Code.
Der Fehler, den ich erhalte, ist “FEHLER beim Binden: Adresse wird bereits verwendet”.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[])
{
    printf("Starting Listener\n");
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     char buffer[256];
     struct sockaddr_in serv_addr, cli_addr;
     int n;
     if (argc < 2) {
         fprintf(stderr,"ERROR, no port provided\n");
         exit(1);
     }
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) 
        error("ERROR opening socket");
     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = atoi(argv[1]);
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");

     printf("about to listen\n");
     listen(sockfd,5);
     printf("finished listening\n");
     clilen = sizeof(cli_addr);
     printf("About to accept\n");

     int i;
     for(i=0; i<100; i++){
         newsockfd = accept(sockfd, 
                 (struct sockaddr *) &cli_addr, 
                 &clilen);

         if (newsockfd < 0) 
             error("ERROR on accept");
         bzero(buffer,256);
         n = read(newsockfd,buffer,255);
         if (n < 0) error("ERROR reading from socket");
         printf("Here is the message: %s\n",buffer);
         n = write(newsockfd,"I got your message",18);
         if (n < 0) error("ERROR writing to socket");
         close(newsockfd);
     }
     close(sockfd);
     return 0; 
}

Benutzeravatar von Chnossos
Chnossos

Setzen Sie die Option, nachdem der Socket erfolgreich initialisiert wurde. So danach:

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) 
    error("ERROR opening socket");

Sie können (mit standard C99 zusammengesetztes Literal Unterstützung):

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0)
    error("setsockopt(SO_REUSEADDR) failed");

Oder:

const int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
    error("setsockopt(SO_REUSEADDR) failed");

Beachten Sie als zusätzlich zu SO_REUSEADDRmüssen Sie möglicherweise festlegen SO_REUSEPORT um das gewünschte Verhalten zu erreichen. Dies geschieht für beide Optionen genau gleich.

  • Ich habe beides ausprobiert und das gleiche Ergebnis erhalten, nachdem ich das Programm beendet und neu gestartet hatte. Dies ist der Fehler: Starting Listener FEHLER beim Binden: Adresse wird bereits verwendet about to listen beendet listening About to accept

    – Benutzer3735849

    14. Juni 2014 um 2:00 Uhr


  • Der Zweck von SO_REUSEADDR/SO_REUSEPORT besteht darin, die Wiederverwendung des Ports zu ermöglichen, selbst wenn der Prozess abstürzt oder beendet wurde.

    – mpromonet

    8. August 2014 um 22:07 Uhr

  • @Chnossos Ich teste gerade auf einem Himbeer-Pi mit Kernel 3.12.25, die Bindung funktioniert gut, auch wenn der Port noch von TIME_WAIT-Sockets verwendet wird (was bei einem Kill SIGKILL auftritt). Hast du den Setsockopt vor dem Bind-Aufruf eingefügt?

    – mpromonet

    9. August 2014 um 17:16 Uhr


Benutzeravatar von mpromonet
mpromonet

Abhängig von der libc-Version kann es erforderlich sein, die Socket-Optionen SO_REUSEADDR und SO_REUSEPORT zu setzen, wie in erläutert Steckdose(7) Dokumentation:

   SO_REUSEPORT (since Linux 3.9)
          Permits multiple AF_INET or AF_INET6 sockets to be bound to an
          identical socket address.  This option must be set on each
          socket (including the first socket) prior to calling bind(2)
          on the socket.  To prevent port hijacking, all of the
          processes binding to the same address must have the same
          effective UID.  This option can be employed with both TCP and
          UDP sockets.

Da diese Socket-Option mit Kernel 3.9 und Raspberry 3.12.x erscheint, muss SO_REUSEPORT gesetzt werden.

Sie können diese beiden Optionen festlegen, bevor Sie bind wie folgt aufrufen:

    int reuse = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0)
        perror("setsockopt(SO_REUSEADDR) failed");

#ifdef SO_REUSEPORT
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) 
        perror("setsockopt(SO_REUSEPORT) failed");
#endif

Ich denke, Sie sollten SO_LINGER-Optionen verwenden (mit Timeout 0). In diesem Fall wird Ihre Verbindung sofort nach dem Schließen Ihres Programms geschlossen; und beim nächsten Neustart kann wieder gebunden werden.

Beispiel:

linger lin;
lin.l_onoff = 0;
lin.l_linger = 0;
setsockopt(fd, SOL_SOCKET, SO_LINGER, (const char *)&lin, sizeof(int));

siehe definition: http://man7.org/linux/man-pages/man7/socket.7.html

SO_LINGER
          Sets or gets the SO_LINGER option.  The argument is a linger
          structure.

              struct linger {
                  int l_onoff;    /* linger active */
                  int l_linger;   /* how many seconds to linger for */
              };

          When enabled, a close(2) or shutdown(2) will not return until
          all queued messages for the socket have been successfully sent
          or the linger timeout has been reached.  Otherwise, the call
          returns immediately and the closing is done in the background.
          When the socket is closed as part of exit(2), it always
          lingers in the background.

Mehr über SO_LINGER: TCP-Option SO_LINGER (Null) – wenn es erforderlich ist

  • Ich glaube, es sollte sagen lin.l_onoff = 1;. Andernfalls deaktivieren Sie die Option vollständig, was bedeutet, dass sie im Hintergrund verweilen würde.

    – Drachenwurzel

    8. März 2019 um 22:00 Uhr

  • Entweder so, wie es in der Antwort oder im Kommentar steht, bekomme ich keine Bindung, wenn ich mein Programm stoppe und sofort starte. Wenn ich SO_REUSEADDR setze, dann funktioniert es super.

    – xaxxon

    22. Mai 2019 um 23:06 Uhr


1417410cookie-checkWie verwende ich setsockopt(SO_REUSEADDR)?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy