Бустер Гнездо идет в живот при закрытии () - PullRequest
0 голосов
/ 03 октября 2018

У нас есть приложение C ++, которое общается с сервером.Он отправляет ему два сообщения, и сервер отвечает на каждое сообщение другим сообщением.Мы используем Boost, но Boost Socket - все приложение - barfs, когда мы пытаемся закрыть сокет.

Вот общее представление о том, что мы делаем:

  1. закодировать сообщение (изменить его в строку)
  2. открыть сокет
  3. отправить сообщение
  4. проверить отправленные байты
  5. проверить сообщение возврата
  6. выключение и закрытие сокета

Поскольку мы отправляем два сообщения, мы делаем это в цикле (очевидно, всего две итерации).

Мы точно знаем, где ошибкапотому что, если мы удалим эту строку, она работает нормально.Это на шаге 5. К сожалению, это довольно важный шаг.Мы не можем найти то, что делаем неправильно, как это исправить.

Вот код:

bool ReallyImportantService::sendMessages( int messageNum ) {

    // ...some error-checking here...

    bool successCode = false;
    for( int i = 0; i < 2; ++i ) {

        successCode = false;

        unique_ptr<boost::asio::ip::tcp::socket> theSocket = connect();

        if( theSocket == nullptr ) {
            theLogger->error( "Could not create socket, could not send input messageNum to service" );
            return successCode;
        }

        string message = encodeMessage( messageNum );

        // send the message
        boost::system::error_code error;
        size_t bytesSent = boost::asio::write(*theSocket,
                                       boost::asio::buffer(message),
                                       boost::asio::transfer_all(), error);

        // inspect the result
        if( !messageNumSendSuccessful(message.length(), bytesSent) ) {
            return successCode;
        }

        // Get the response message
        string response;
        boost::system::error_code e;
        boost::asio::streambuf buffer;

        // this is step #5 above, the line that kills it. But it responds with no errors
        boost::asio::read_until(*theSocket, buffer, "\0", e);

        if( e.value() == boost::system::errc::success ) {
            istream str(&buffer);
            getline(str, response);

            // validate response
            successCode = messageAckIsValid( response, messageNum );
        }
        else {
            theLogger->error( "Got erroneous response from server when sending messageNum" );
        }

        // close it all up
        boost::system::error_code eShut;
        theSocket->shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, eShut);
        // We never get an error code here, all clean

        try {
            boost::system::error_code ec;

            // This is where it all goes belly-up. It doesn't throw an exception, doesn't return an 
            // error-code. Stepping through, we can see the call stack shows a Segmentation fault, 
            // but we don't know what could be causing this.
            theSocket->close( ec );
        }
        catch(boost::system::system_error& se) {
            theLogger->error( "sendMessages() barfed on close! " + string(se.what()) );
        }
        catch( ... ) {
            theLogger->error( "sendMessages() barfed on close! " );
        }
    }
    return successCode;
}

string ReallyImportantService::encodeMessage( int messageNum ) {

    // Encode the message
    stringstream ss;
    ss << "^FINE=";
    ss << to_string(messageNum) << "\n";
    string message = ss.str();

    theLogger->info( message );

    return message;
}

unique_ptr<boost::asio::ip::tcp::socket> ReallyImportantService::connect() {
    // Addresses from configuration
    string address( server_ip );
    string port( server_port );

    // Resolve the IP address
    boost::asio::io_service ioService;
    boost::asio::ip::tcp::resolver resolver(ioService);
    boost::asio::ip::tcp::resolver::query query(address, port);
    boost::asio::ip::tcp::resolver::iterator ep_iterator = resolver.resolve(query);

    // create the socket
    unique_ptr<boost::asio::ip::tcp::socket> theSocket = make_unique<boost::asio::ip::tcp::socket>(ioService);

    // not sure if this is necessary, but couldn't hurt; we do reuse the IP address the second time around
    boost::system::error_code ec;
    theSocket->set_option(boost::asio::socket_base::reuse_address(true), ec);

    // Connect
    try {

        boost::asio::connect(*theSocket, ep_iterator);

    } catch(const boost::system::system_error &e){
        theSocket = nullptr;
        theLogger->error( "Exception while attempting to create socket: " + string(e.what()) );
    } catch(const exception &e){
        theSocket = nullptr;
        theLogger->error( "Exception while attempting to create socket: " + string(e.what()) );
    }

    return theSocket;
}

Вот стек вызовов, который мы получаем, когда он выдает ошибку:

(Suspended : Signal : SIGSEGV:Segmentation fault)   
    pthread_mutex_lock() at 0x7ffff7bc8c30  
    boost::asio::detail::posix_mutex::lock() at posix_mutex.hpp:52 0x969072 
    boost::asio::detail::scoped_lock<boost::asio::detail::posix_mutex>::scoped_lock() at scoped_lock.hpp:36 0x980b66    
    boost::asio::detail::epoll_reactor::free_descriptor_state() at epoll_reactor.ipp:517 0x96c6fa   
    boost::asio::detail::epoll_reactor::deregister_descriptor() at epoll_reactor.ipp:338 0x96bccc   
    boost::asio::detail::reactive_socket_service_base::close() at reactive_socket_service_base.ipp:103 0xb920aa 
    boost::asio::stream_socket_service<boost::asio::ip::tcp>::close() at stream_socket_service.hpp:151 0xb975e0 
    boost::asio::basic_socket<boost::asio::ip::tcp, boost::asio::stream_socket_service<boost::asio::ip::tcp> >::close() at basic_socket.hpp:339 0xb94f0d    
    ReallyImportantService::sendMessages() at ReallyImportantService.cc:116 0xb8ce19    
    <...more frames...> 

Мы создали минимальную реализацию, которая просто:

  1. Создает сокет
  2. Выключает сокет
  3. Закрывает сокет

И это прекрасно работает.Мы поместили его в цикл и можем без проблем выполнить десятки итераций.

Мы используем Eclipse CDT и gcc для компиляции.

Есть идеи, что может произойти?

1 Ответ

0 голосов
/ 03 октября 2018

Вы нарушили кардинальное правило.

io_service должен пережить все созданные на нем объекты.

Ваша функция connect() создает io_service, создает сокет наон и возвращает сокет (обернутый в unique_ptr).Затем io_service уничтожается.

С этого момента все ставки отключены, потому что сокет будет использовать сервисный объект сокета, связанный с только что уничтоженным io_service.Этот сервис сокетов теперь просто память с неопределенными значениями в нем.Вам (не) повезло, что программа зашла так далеко до segfault.

Как правило, вам потребуется один io_service на приложение.Все объекты, которые в нем нуждаются, должны иметь ссылку на него.

Ваша функция соединения становится:

bool connect(boost::asio::ip::tcp& theSocket) {
    // Addresses from configuration
    string address( server_ip );
    string port( server_port );

    // Resolve the IP address
    boost::asio::ip::tcp::resolver resolver(theSocket.get_io_service());
    boost::asio::ip::tcp::resolver::query query(address, port);
    boost::asio::ip::tcp::resolver::iterator ep_iterator = resolver.resolve(query);

    // not sure if this is necessary, but couldn't hurt; we do reuse the IP address the second time around
    boost::system::error_code ec;
    theSocket.set_option(boost::asio::socket_base::reuse_address(true), ec);

    // Connect
    try {

        boost::asio::connect(theSocket, ep_iterator);

    } catch(const boost::system::system_error &e){
        theSocket = nullptr;
        theLogger->error( "Exception while attempting to create socket: " + string(e.what()) );
        return false;
    } catch(const exception &e){
        theSocket = nullptr;
        theLogger->error( "Exception while attempting to create socket: " + string(e.what()) );
        return false;
    }

    return true;
}

bool sendMessages(boost::asio::io_service& ios, int messageNum)
{
    boost::asio::ip::tcp::socket theSocket(ios);
    auto ok = connect(theSocket);

    // ... carry on ...

}
  • Предпочитает хранить ссылки на сокеты и т. Д., Когда это возможно.Оборачивание их в unique_ptr - запутанный дополнительный слой косвенности.

  • Начиная с c ++ 11 и последних версий boost сокеты asio являются подвижными.Вы можете вернуть их по значению, в отличие от передачи ссылки, как я сделал.

  • Я заметил, что в коде есть смесь обработки ошибок исключения и исключения.Вы, вероятно, хотите придерживаться одного или другого (на мой взгляд, обработка ошибок на основе исключений более понятна, но это не универсальное представление).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...