Wiederverwendung von boost::asio::ip::tcp::acceptor



  • Huhu!

    Folgender Code-Abschnitt:

    void server::run(tcp::endpoint endpoint, int backlog){
        if(acceptor.is_open()){
            acceptor.close();
            connections.clear();
            acceptor.get_io_service().run();
        }
    
        acceptor.open(endpoint.protocol());
    
        acceptor.set_option(tcp::socket::reuse_address(true));
        acceptor.bind(endpoint);
        acceptor.listen(backlog);
    
        accept();
    }
    
    void server::accept(){
        std::shared_ptr<tcp::socket> socket = std::make_shared<tcp::socket>(
            acceptor.get_io_service()
        );
    
        acceptor.async_accept(
            *socket,
            boost::bind(&server::handle_accept, this, socket, boost::asio::placeholders::error)
        );
    }
    

    Die run()-Methode soll sozusagen den Server (neu)starten und anschließend auf Klienten warten. Wenn die run()-Methode dann ein weiteres Mal aufgerufen wird, sollen alle (in einem Container gehaltenen) Verbindungen mitsamt dem Acceptor geschlossen (siehe Zeilen 3 und 4) und den Acceptor dann mit frischen Werten gefüttert werden (siehe Zeilen 8 - 12).
    Wenn ich die run()-Methode jedoch ein zweites Mal aufrufe, wirft die async_accept()-Methode eine "Bad file descriptor"-Exception und ich versteh' grad nicht warum. Versucht habe ich bisher einen acceptor::cancel()-Aufruf vor dem schließen des Acceptors, sowie folgendes:

    if(acceptor.is_open()){
        connections.clear();
    
        boost::asio::io_service& io_service = acceptor.get_io_service();
    
        acceptor.~basic_socket_acceptor();
        new(&acceptor) tcp::acceptor{io_service};
    
        io_service.run();
    }
    

    Beides wirft die gleiche "Bad file descriptor"-Exception.
    Jemand eine Ahnung?



  • Evtl. bin ich ein alter Sack und gegenüber Provokationen nicht mehr so resistent wie früher...
    Aber bei Deinem Nick habe ich noch nicht mal Lust Deinen Post zu lesen.

    Schönen Gruß,
    FW



  • Warum innerhalb von server den acceptor wiederverwenden (noch dazu mit placement new)? Wirf einfach die Instanz von server weg (=im dtor die Verbindungen schliessen, etc.) und erzeuge eine neue Instanz.



  • Furble Wurble schrieb:

    Evtl. bin ich ein alter Sack und gegenüber Provokationen nicht mehr so resistent wie früher...
    Aber bei Deinem Nick habe ich noch nicht mal Lust Deinen Post zu lesen.

    Schönen Gruß,
    FW

    Nicht doch. 😕
    Damit wollte ich niemanden provozieren.Sorry wenns so ankam.



  • theta schrieb:

    Warum innerhalb von server den acceptor wiederverwenden (noch dazu mit placement new)? Wirf einfach die Instanz von server weg [...] und erzeuge eine neue Instanz.

    Das habe ich soeben versucht. Wirft die gleiche Exception. Hab die Server-Instanz einem std::shared_ptr<> überlassen. Jedesmal, wenn der Server neu gestartet werden soll, wird srv.reset(new server{io_service}); vor der run()-Methode aufgerufen.

    theta schrieb:

    (=im dtor die Verbindungen schliessen, etc.)

    Das sollte doch eigentlich automatisch über die Bühne laufen. Der Destruktor des Acceptors wird aufgerufen und der Destruktor des std::vector<connection> ruft alle Destruktoren seiner Elemente auf, unter anderem auch die von tcp::socket. Auch wenn ich den Destruktor selber schreibe (eben probiert), wird die gleiche Exception geworfen. Hier noch ein Stückchen Code, falls der noch was aussagen könnte:

    void start_server(...){
    ...
    
        srv.reset(new server{io_service});
        // io_service.run();  <=== Dies erzeugt eine "Operation canceled"-Exception
    
        srv.run(tcp::endpoint{tcp::v4(), port}, backlog);
    }
    


  • Ich schlage vor, dass du ein vollständiges Bsp. postest, welches den Fehler zeigt. Das hilft sicher am meisten um dein konkretes Problem zu lösen.

    Hier trotzdem einige Gedanken...

    In der start_server(..) Funktion ist die Reihenfolge falsch zu sein:

    Die korrekte Reihenfolge muss sein:

    1. acceptor erzeugen (inkl. öffnen, listen etc.) und async_accept(..) aufrufen -> es werden noch keine Verbindungsanfragen prozessiert
    2. io_service::run() aufrufen -> erst jetzt prozessiert der Server Verbindungsanfragen
      ➡ io_service::run kehrt zurück, sobald keine Aufträge (=pending async_accept(..) mehr offen sind, d.h. du musst es rekursiv aufrufen fall mehr als eine Verbindung angenommen werden soll)

    Auf deine start_server(..) Funktion angewendet:

    void start_server(...){
      // ...
      boost::asio::io_service io_service;
      srv.reset(new server{io_service});
      srv->run(tcp::endpoint{tcp::v4(), port}, backlog);
      io_service.run();
    }
    

    Der einzige Grund warum es server::run(..) brauchen könnte, ist wenn du mit enable_shared_from_this arbeiten tätest - falls nicht: ab in den Konstruktor damit.



  • Hmhm, okay.

    Ich hab mein Projekt etwas umdesigned, sodass es nun funktioniert.
    Die genaue Ursache, die die Exception geworfen hat, kenne ich leider nicht. Jedenfalls klappt es nun, nachdem boost::asio::io_service Member von server ist und die server -Instanz bei jedem Neustart wieder resettet wird.

    Das Problem ist somit gelöst.
    Danke für die Hilfe!


Anmelden zum Antworten