2010-06-17 16 views
17

Moja aplikacja kliencka używa boost::asio::ip::tcp::socket do łączenia się ze zdalnym serwerem. Jeśli aplikacja utraci połączenie z tym serwerem (np. Z powodu awarii serwera lub zamknięcia), chciałabym spróbować ponownie połączyć się w regularnych odstępach czasu, dopóki się to nie uda.Jak mogę ponownie podłączyć gniazdo boost :: po odłączeniu?

Co muszę zrobić po stronie klienta, aby wyczyścić obsługę rozłączenia, posprzątać, a następnie wielokrotnie próbować ponownych połączeń?

Obecnie interesujące fragmenty mojego kodu wyglądają mniej więcej tak.

I connect tak:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Attempt connection 
    socket.connect(server_endpoint, errorcode); 

    if (errorcode) 
    { 
     cerr << "Connection failed: " << errorcode.message() << endl; 
     mydisconnect(); 
    } 
    else 
    { 
     isConnected = true; 

     // Connected so setup async read for an incoming message. 
     startReadMessage(); 

     // And start the io_service_thread 
     io_service_thread = new boost::thread(
      boost::bind(&MyClient::runIOService, this, boost::ref(io_service))); 
    } 
    return (isConnected) 
} 

Jeżeli metoda runIOServer() tylko:

void MyClient::runIOService(boost::asio::io_service& io_service) 
{ 
    size_t executedCount = io_service.run(); 
    cout << "io_service: " << executedCount << " handlers executed." << endl; 
    io_service.reset(); 
} 

A jeśli któryś z obsługą asynchroniczny odczytu zwróci błąd następnie po prostu wywołanie tej metody disconnect:

void MyClient::mydisconnect(void) 
{ 
    boost::system::error_code errorcode; 

    if (socket.is_open()) 
    { 
     // Boost documentation recommends calling shutdown first 
     // for "graceful" closing of socket. 
     socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, errorcode); 
     if (errorcode) 
     { 
      cerr << "socket.shutdown error: " << errorcode.message() << endl; 
     } 

     socket.close(errorcode); 
     if (errorcode) 
     { 
      cerr << "socket.close error: " << errorcode.message() << endl; 
     }  

     // Notify the observer we have disconnected 
     myObserver->disconnected();    
    } 

.. który próbuje łaski całkowicie odłączyć, a następnie powiadomi obserwatora, który rozpocznie wywoływanie connect() w pięciosekundowych odstępach czasu, aż zostanie ponownie podłączony.

Czy jest coś jeszcze, co muszę zrobić?

Obecnie wygląda na to, że zadziała. Jeśli zabiję serwer, do którego jest on podłączony, otrzymuję oczekiwany błąd "End of file" w moich procedurach odczytu i wywołanie mydisconnect() bez żadnych problemów.

Ale gdy następnie próbuje ponownie połączyć się i nie działa, widzę raport "socket.shutdown error: Invalid argument". Czy to dlatego, że próbuję zamknąć gniazdo, które nie ma odczytu/zapisu oczekujących na to? A może to coś więcej?

+0

Jakie jest Twoje uzasadnienie wywołania zamknięcia, jeśli wykryłeś już koniec drugiego końca połączenia? –

+0

@samm: Czy to nie jest zalecane? Myślałem, że mogą być oczekujące operacje na gnieździe, które muszą być anulowane przy pomocy 'shutdown()'. Głównie robię to jednak dla uproszczenia: ta sama metoda 'mydisconnect()' jest wywoływana, jeśli chcę normalnie rozłączyć, lub jeśli jakiekolwiek operacje asynchroniczne zwrócą błąd. – GrahamS

+0

Nie jestem pewien, czy jest to zalecane, czy nie. Gdzie będą te oczekujące dane lub operacje? Drugi koniec połączenia nie ma. –

Odpowiedz

22

Musisz utworzyć nowy boost::asio::ip::tcp::socket przy każdym ponownym połączeniu. Najprostszym sposobem na to jest prawdopodobnie po prostu przydzielić gniazdo na stercie za pomocą boost::shared_ptr (prawdopodobnie można też uciec z scoped_ptr, jeśli twoje gniazdo jest całkowicie zamknięte w klasie). Np:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Attempt connection 
    // socket is of type boost::shared_ptr<boost::asio::ip::tcp::socket> 
    socket.reset(new boost::asio::ip::tcp::socket(...)); 
    socket->connect(server_endpoint, errorcode); 
    // ... 
} 

Potem, kiedy mydisconnect nazywa, można cofnąć przydział gniazdo:

void MyClient::mydisconnect(void) 
{ 
    // ... 
    // deallocate socket. will close any open descriptors 
    socket.reset(); 
} 

Błąd widzisz jest prawdopodobnie wynikiem OS sprzątanie deskryptor po ciebie” nazywa się close. Po wywołaniu close, a następnie spróbuj connect na tym samym gnieździe, prawdopodobnie próbujesz podłączyć niepoprawny deskryptor pliku. W tym momencie powinien pojawić się komunikat o błędzie zaczynający się od "Połączenie nie powiodło się: ..." w oparciu o twoją logikę, ale potem zadzwonisz pod numer mydisconnect, który prawdopodobnie następnie próbuje wywołać shutdown na niepoprawnym deskryptorze pliku. Błędne koło!

+0

Próba zniechęcenia do przenoszenia nigdy nie jest dobrym pomysłem, zwłaszcza, gdy nie znasz nawet docelowego systemu operacyjnego lub używanego do programowania, o to chodzi! – Geoff

+0

@Geoff: to słuszna sprawa ... Zmieniłem moją odpowiedź. – bjlaub

+0

Dzięki temu podejściu dobrze mi poszło. Zmodyfikowałem połączenie tak, aby zawsze tworzyć nowe gniazdo, a następnie wywoływać '.reset' na' shared_ptr' podczas opisywania. Jeśli próba połączenia się nie powiedzie, po prostu wołam 'socket-> close()' i zostawiam to. Opuściłem metodę 'disconnect', tak jak to było w przypadku połączenia * grzecznościowego *' shutdown'. – GrahamS

1

Zrobiłem coś podobnego za pomocą Boost.Asio w przeszłości. Używam metod asynchronicznych, więc ponowne połączenie zazwyczaj pozwala na wyjście z mojego istniejącego obiektu IP :: tcp :: socket, a następnie utworzenie nowego dla wywołań async_connect. Jeśli async_connect się nie powiedzie, używam timera, aby trochę uśpić, a następnie spróbować ponownie.

+0

dziękuje, więc czy po prostu wywołujesz 'socket.close()', gdy wykryjesz błąd, a następnie utworzysz nowy? – GrahamS

+0

deskryptor jest zamykany, gdy obiekt IP :: tcp :: socket wykracza poza zakres. –

4

Dla jasności tutaj jest ostatecznym podejście Kiedyś (ale to jest na podstawie odpowiedzi bjlaub, więc proszę dać żadnych upvotes do niego):

oświadczyłem człon socket jako scoped_ptr:

boost::scoped_ptr<boost::asio::ip::tcp::socket> socket; 

Potem zmodyfikowano moją metodę connect być:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Create new socket (old one is destroyed automatically) 
    socket.reset(new boost::asio::ip::tcp::socket(io_service)); 

    // Attempt connection 
    socket->connect(server_endpoint, errorcode); 

    if (errorcode) 
    { 
     cerr << "Connection failed: " << errorcode.message() << endl; 
     socket->close(); 
    } 
    else 
    { 
     isConnected = true; 

     // Connected so setup async read for an incoming message. 
     startReadMessage(); 

     // And start the io_service_thread 
     io_service_thread = new boost::thread(
      boost::bind(&MyClient::runIOService, this, boost::ref(io_service))); 
    } 
    return (isConnected) 
} 
0

próbowałem zarówno metodę close() oraz metodę zamykania i są po prostu dla mnie trudne. Close() może rzucić błąd, który musisz złapać i jest niegrzeczny sposób, aby zrobić to, co chcesz :) i shutdown() wydaje się być najlepszy, ale na oprogramowanie wielowątkowe, uważam, że może być wybredny. Najlepszym sposobem jest, jak Sam powiedział, pozwolić mu wyjść poza zakres. Jeśli gniazdo jest członkiem klasy, możesz 1) przeprojektować, aby klasa używała obiektu "połączenia" do owinięcia gniazda i zwolnienia go z zakresu lub 2) zawinięcia go w inteligentny wskaźnik i zresetowania inteligentnego wskaźnika. Jeśli używasz doładowania, w tym shared_ptr jest tanie i działa jak urok. Nigdy nie miałem problemu z czyszczeniem gniazd, robiąc to z shared_ptr. Tylko moje doświadczenie.

+0

* musisz * wywołać 'close(),' 'tricky' lub nie. W przeciwnym razie masz wyciek zasobów. Nie ma w tym nic niegrzecznego. A "shutdown()' nie jest alternatywą dla 'close()'. – EJP

Powiązane problemy