Как мне чисто повторно подключить boost :: socket после отключения?

Мое клиентское приложение использует boost::asio::ip::tcp::socket для подключения к удаленному серверу. Если приложение теряет соединение с этим сервером (например, из-за сбоя сервера или его выключения), я хотел бы, чтобы оно пыталось повторно подключиться через регулярные промежутки времени, пока оно не будет успешным.

Что мне нужно сделать на стороне клиента, чтобы аккуратно обработать отключение, привести в порядок и затем неоднократно повторять попытки подключения?

В настоящее время интересные фрагменты моего кода выглядят примерно так.

Мне connect нравится это:

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)
}

Где метод runIOServer() просто:

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();
}

И если какой-либо из обработчиков асинхронного чтения возвращает ошибку, они просто вызывают этот 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();            
    }

..который пытается корректно отключиться, а затем уведомляет наблюдателя, который начинает вызывать connect() с пятисекундными интервалами, пока не восстановит соединение.

Что еще мне нужно сделать?

В настоящее время это похоже работает. Если я убиваю сервер, к которому он подключен, я получаю ожидаемую "End of file" ошибку в моих обработчиках чтения, и mydisconnect() вызывается без каких-либо проблем.

Но когда он затем пытается повторно подключиться и терпит неудачу, я вижу отчет "socket.shutdown error: Invalid argument". Это просто потому, что я пытаюсь выключить сокет, на котором нет ожидающих чтения / записи? Или это нечто большее?


person GrahamS    schedule 17.06.2010    source источник
comment
Каково ваше объяснение для вызова выключения, если вы уже обнаружили, что другой конец соединения закрывается?   -  person Sam Miller    schedule 17.06.2010
comment
@samm: Это не рекомендуется? Я подумал, что на сокете могут быть отложенные операции, которые нужно отменить с помощью shutdown(). В основном я делаю это для простоты: тот же метод mydisconnect() вызывается, если я хочу нормально отключиться или если какие-либо асинхронные операции возвращают ошибку.   -  person GrahamS    schedule 18.06.2010
comment
Я не уверен, рекомендуется это или нет. Куда пойдут ожидающие данные или операции? Другой конец соединения отсутствует.   -  person Sam Miller    schedule 18.06.2010
comment
@ SamMiller В этом нет необходимости. Это рекомендуется в широко неправильно понимаемой статье MSDN, цель которой - показать вам, как добиться синхронизированного закрытия обоими одноранговыми узлами, но в этом случае вам следует отключиться только для вывода. close() - это все, что требуется. Данные, которые еще находятся в полете, по-прежнему доставляются.   -  person user207421    schedule 29.03.2017


Ответы (5)


Вам нужно создавать новый boost::asio::ip::tcp::socket каждый раз при повторном подключении. Самый простой способ сделать это, вероятно, - просто выделить сокет в куче с помощью boost::shared_ptr (вы, вероятно, также можете обойтись с scoped_ptr, если ваш сокет полностью инкапсулирован внутри класса). Например.:

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);
    // ...
}

Затем, когда вызывается mydisconnect, вы можете освободить сокет:

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

Ошибка, которую вы видите, вероятно, является результатом того, что ОС очистила файловый дескриптор после того, как вы вызвали close. Когда вы вызываете close, а затем пытаетесь connect на том же сокете, вы, вероятно, пытаетесь подключить недопустимый файловый дескриптор. На этом этапе вы должны увидеть сообщение об ошибке, начинающееся с «Ошибка подключения: ...» в зависимости от вашей логики, но затем вы вызываете mydisconnect, который, вероятно, затем пытается вызвать shutdown для недопустимого файлового дескриптора. Порочный круг!

person bjlaub    schedule 18.06.2010
comment
Попытки воспрепятствовать переносимости никогда не были хорошей идеей, особенно если вы даже не знаете целевую ОС или ту, которая используется для разработки, если на то пошло !! - person Geoff; 19.06.2010
comment
Спасибо, этот подход хорошо сработал для меня. Я изменил соединение, чтобы всегда создавать новый сокет, а затем вызывать .reset на shared_ptr, как вы описываете. Если попытка подключения не удалась, я просто позвоню socket->close() и оставлю все как есть. Я оставил метод disconnect таким, каким он был с вызовом вежливо shutdown. - person GrahamS; 25.06.2010
comment
Я не вижу метода сброса для boost :: asio :: ip :: tcp :: socket ?? См. boost.org/doc/ libs / 1_55_0 / doc / html / boost_asio / reference / ip__tcp / - person Anonymous; 26.11.2015
comment
@Anonymous Это метод std :: shared_ptr, который заменяет управляемый объект аргументом: en.cppreference.com/w/cpp/memory/shared_ptr/reset - person robsn; 05.04.2017

Для ясности вот последний подход, который я использовал (но он основан на ответе bjlaub, поэтому, пожалуйста, дайте ему любые положительные голоса):

Я объявил участника socket как scoped_ptr:

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

Затем я изменил свой connect метод следующим образом:

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)
}

Примечание: этот вопрос был первоначально задан и дан ответ еще в 2010 году, но если вы сейчас используете C ++ 11 или более позднюю версию, то std::unique_ptr обычно будет лучшим выбором, чем boost::scoped_ptr

person GrahamS    schedule 25.06.2010
comment
Я думаю, было бы неплохо порекомендовать использовать std::unique_ptr вместо boost::scoped_ptr, поскольку это лучший вариант, а C ++ 11 широко доступен. - person Ivan_Bereziuk; 22.06.2018
comment
@Ivan_Bereziuk: Я отредактировал свой ответ. Первоначально он был опубликован еще в 2010 году, до того, как C ++ 11 стал широко доступным. - person GrahamS; 22.06.2018

Начиная с C ++ 11 вы можете написать:

decltype(socket)(std::move(socket));
// reconnect socket.

Вышеупомянутое создает локальный экземпляр перемещения типа сокета, конструируя его из сокета.

Перед следующей строкой уничтожается безымянный локальный экземпляр, оставляя сокет в состоянии «только что созданный из io_service».

См .: https://www.boost.org/doc/libs/1_63_0/doc/html/boost_asio/reference.html#boost_asio.reference.basic_stream_socket.basic_stream_socket.overload5

person user771723    schedule 16.08.2018
comment
любезно рассмотрите возможность добавления дополнительной информации в свой ответ - person Inder; 16.08.2018

Раньше я делал нечто подобное, используя Boost.Asio. Я использую асинхронные методы, поэтому при повторном подключении мой существующий объект ip :: tcp :: socket обычно выходит за пределы области видимости, а затем создается новый для вызовов async_connect. Если async_connect не работает, я использую таймер, чтобы немного засыпать, а затем повторите попытку.

person Sam Miller    schedule 17.06.2010
comment
спасибо, так что вы просто вызываете socket.close(), когда обнаруживаете ошибку, а затем создаете новую? - person GrahamS; 18.06.2010
comment
дескриптор закрывается, когда объект ip :: tcp :: socket выходит за пределы области видимости. - person Sam Miller; 18.06.2010

Я пробовал как метод close (), так и метод выключения, и они для меня очень сложны. Close () может вызвать ошибку, которую вам нужно поймать, и это грубый способ делать то, что вы хотите :) и shutdown () кажется лучшим, но в многопоточном программном обеспечении я считаю, что это может быть суетливым. Так что, как сказал Сэм, лучший способ - выпустить это из поля зрения. Если сокет является членом класса, вы можете 1) перепроектировать так, чтобы класс использовал объект «соединение», чтобы обернуть сокет и позволить ему выйти из области видимости, или 2) обернуть его интеллектуальным указателем и сбросить интеллектуальный указатель. Если вы используете boost, включение shared_ptr дешево и работает как шарм. Никогда не было проблемы с очисткой сокета, выполняющей это с помощью shared_ptr. Просто мой опыт.

person William Symionow    schedule 07.01.2013
comment
Вы должны называть close(), "хитрым" или нет. В противном случае у вас будет утечка ресурсов. В этом нет ничего грубого. И shutdown() не альтернатива close(). - person user207421; 29.03.2017