Как аккуратно подключить разъем boost :: socket после отключения? - PullRequest
25 голосов
/ 17 июня 2010

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

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

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

I 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". Это только потому, что я пытаюсь отключить сокет, который не ожидает чтения / записи? Или это нечто большее?

Ответы [ 5 ]

26 голосов
/ 19 июня 2010

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

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 на том же сокете, вы, вероятно, пытаетесь подключить неверный дескриптор файла. В этот момент вы должны увидеть сообщение об ошибке, начинающееся с «Connection failed: ...», основываясь на вашей логике, но затем вы вызываете mydisconnect, который, вероятно, затем пытается вызвать shutdown для недопустимого дескриптора файла. Порочный круг!

7 голосов
/ 25 июня 2010

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

Я объявил член 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

2 голосов
/ 17 июня 2010

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

0 голосов
/ 16 августа 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

0 голосов
/ 08 января 2013

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

...