Port auch nach Absturz besetzt



  • Ich habe eine Anwendung geschrieben, die auf Port 4000 lauscht. Nun enthält das Programm irgendwo einen Fehler und wenn ich einen Client verbinde stürzt die Anwendung ab. Allerdings bleibt der Port besetzt. Die Anwendung ist nicht mehr aktiv, sie ist beendet (Zumindest sagt ps -A das). Wenn ich dann die Anwendung erneut starte kann kein Port erstellt werden, weil er angeblich schon besetzt ist. Nach einiger Zeit kann ich das Programm allerdings wieder starten (schwer reproduzierbar, weiß nicht genau wie lange es dauert).

    Ich führe das Programm mit ganz normalen Benutzerrechten aus, nicht als root. Das System ist ein SuSE 9.0 mit Kernel 2.4.21.243-athlon



  • jep, "normaler Weise" ist so! Mach's deshalb so:

    struct sockaddr_in a_sockaddr;
      int sock;
      // Unverbundenen Socket erzeugen
      if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
        perror("(socket) ");
        return;
      }
      int opt = 1;
      if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) == -1) {
        perror("(setsockopt) ");
        return;
      }
      a_sockaddr.sin_family      = AF_INET;
      a_sockaddr.sin_port        = htons(4000);
      a_sockaddr.sin_addr.s_addr = INADDR_ANY;
      // Adresse mit Socket binden
      if (bind(sock, (struct sockaddr*) &a_sockaddr, sizeof(struct sockaddr_in)) == -1) {
        perror("(bind) ");
        return;
      }
      // Neue Verbindungen annehmen
      if(listen(sock, SOMAXCONN) == -1) {
        perror("(listen) ");
        return;
      }
    

    Dann sollte es klappen, den Port anschliessend wieder erneut zu belegen. Die Zauberfunktion heisst: setsockopt mit dem Wert SO_REUSEADDR



  • der port (bzw. der socket) wird vom kernel offen gehalten um noch evtl
    eintreffende pakete der gegenseite zu verarbeiten
    (das verhindert z.b. das du einen neuen socket anlegst und "alte"
    pakete erhaelst die an den socket gerichtet sind).

    du kannst den socket in einem solchen fall dennoch direkt
    verwenden ueber SO_REUSEADDR und SO_REUSEPORT.

    int on =1;
    setsockopt(socket,SOL_SOCKET,SO_REUSEPORT,&on,sizeof(on));
    

    edit: hm. gleichzeitig nen beitrag mit selben inhalt.
    naja fast. bei REUSEPORT musst du als level SOL_SOCKET nehmen 🙂



  • SO_REUSEPORT kennt er bei mir nicht... Ist da 'ne spezielle Header außer <sys/socket.h> und <sys/types.h> nötig?



  • Von REUSEPORT hab ich auch noch nix gehört. Aber SO_REUSEADDR allein reicht, damit ist das Problem behoben.



  • Ich bekomme immernoch den selben Fehler, auch wenn ich SO_REUSEADDR benutze... Der Fehler tritt bei bind() auf.



  • Was tritt bei Dir denn für ein Fehler auf?
    Du schreibst, dass der Compiler bei Dir (auch) "SO_REUSEADDR" nicht kennt und der Fehler bei "bind" auftritt? Aber bei "bind" kommt "SO_REUSEADDR" doch gar nicht vor, sondern bei "setsockopt"!
    Hast Du nun ein Compilerproblem oder ein Laufzeitproblem?



  • Das musst Du falsch verstanden haben, oder ich habe mich missverständlich ausgedrückt... SO_REUSEADDR bei setsockopt funktioniert. Kompilieren ist überhaupt garkein Problem. Ich habe ein Laufzeitproblem. Wenn das Programm einmal gestartet wurde und abgestürzt ist, dann ist der Port für eine gewisse Zeit immernoch besetzt, dass heißt die neue Instanz des Programms kann bind() nicht richtig ausführen.



  • Komisch ?!?
    Also wenn Du den Sourcecode oben, so wie ich das Beispiel geschrieben habe nimmst und übersetzt (oben noch um includes erweitern und unten um eine sinnvolle Accept-Loop), dann kann ich das Programm mit Strg+C oder auch mit "kill -9 <pid>" abschiessen und sofort wieder funktionsfähig startet. Ich würde darauf tippen, das der Fehler dann noch woanders liegen muss.
    Ich hatte auch mal das Problem, daß ein Programm von mir abgestürzt ist und dann Dein Problem zeigte, aber mit der obigen Änderung ging es dann - probier's mal an einer kleinen Testanwendung aus. In meinem Fall habe ich aber auch mit Threads gearbeitet, verwendest Du vielleicht Prozesse (d.h. fork) in der Accept-Loop? Vielleicht laufen dann noch Zombi-Prozesse?



  • wischmop2 schrieb:

    Komisch ?!?
    Also wenn Du den Sourcecode oben, so wie ich das Beispiel geschrieben habe nimmst und übersetzt (oben noch um includes erweitern und unten um eine sinnvolle Accept-Loop), dann kann ich das Programm mit Strg+C oder auch mit "kill -9 <pid>" abschiessen und sofort wieder funktionsfähig startet. Ich würde darauf tippen, das der Fehler dann noch woanders liegen muss.

    wie lange der port nach dem programmende noch im listen zustand bleibt
    hangt vom jeweiligen system ab. es ist durchaus moeglich, dass dein
    system ein sofortiges wiederbelegen des ports erlaubt, ein
    anderes aber erst nach einer bestimmten zeitspanne.

    SO_REUSEPORT scheinen wohl nicht alle systeme zu unterstuetzen.



  • wischmop2 schrieb:

    In meinem Fall habe ich aber auch mit Threads gearbeitet, verwendest Du vielleicht Prozesse (d.h. fork) in der Accept-Loop? Vielleicht laufen dann noch Zombi-Prozesse?

    Ich benutze in der Anwendung pthreads (womit es auch imemr wieder Fehler gibt siehe http://www.c-plusplus.net/forum/viewtopic.php?t=90499 ). Sicher ist auf jeden Fall, dass kein close() für den Socket aufgerufen wird. Hier vermnute ich auch den Fehler. Aber trotzdem: Ist der socket nicht ein Kernelobjekt, dass - falls der Prozess nicht mehr existiert, der es angefordert hat - wieder gelöscht wird? Oder habe ich da was ganz falsch verstanden?



  • nochmal:
    der socket bleibt noch eine weile offen, um noch evtl. vorhandene
    pakete, die grade "auf dem weg sind" abzufangen.
    sonst koennte es passieren, dass du auf einem grade erzeugten socket
    alte pakete erhaelts.



  • Hast Du denn nun mal ein kleines Testprogramm mit den paar Codezeilen von mir geschrieben?
    Es empfiehlt sich nicht immer, an einem größeren Programm eine Korrektur vorzunehmen und dann wenn es immer noch nicht funktioniert zu glauben, dass das es das es noch eine andere Lösung geben muss, denn eigentlich ist es die beschriebene Lösung. In C/C++ können manchmal durch verbogene Zeiger, bereits gelöschte Objekte etc. Probleme an Stellen auftreten, an die man in den kühnsten Träumen nicht zu denken mag.
    Schreib mal das Testprogramm, "kill" es dann und vergleich das Verhalten mit mit Deinem ursprünglichen Progamm (z.B. auch mal killen).
    SO_REUSEPORT habe ich übrigens bei mir in der socket.h gefunden, allerdings auskommentiert:
    /* To add :#define SO_REUSEPORT 15 */
    Kannst es damit ja auch nochmal testen, indem Du einfach den Wert 15 nimmst.


Anmelden zum Antworten