2011-07-24 20 views
5

Używam Boost.Asio do operacji sieciowych, muszą (i faktycznie, nie ma złożonych struktur danych ani nic) pozostać dość niskim poziomem, ponieważ nie stać mnie na luksus związany z serializacją (a biblioteki, które znalazłem, które dawały wystarczająco dobre wyniki, wydawały się źle dopasowane do mojego przypadku).Funkcja wywołania zwrotnego Asio nie jest wywoływana

Problem polega na tym, że piszę asynchronicznie robię od klienta (w QT, ale to prawdopodobnie powinno być tutaj nieistotne). Wywołanie zwrotne określone w async_write nie jest wywoływane, nigdy, i mam całkowitą stratę, dlaczego. Kod to:

void SpikingMatrixClient::addMatrix() { 
    std::cout << "entered add matrix" << std::endl; 
    int action = protocol::Actions::AddMatrix; 
    int matrixSize = this->ui->editNetworkSize->text().toInt(); 
    std::ostream out(&buf); 
    out.write(reinterpret_cast<const char*>(&action), sizeof(action)); 
    out.write(reinterpret_cast<const char*>(&matrixSize), sizeof(matrixSize)); 
    boost::asio::async_write(*connection.socket(), buf.data(), 
          boost::bind(&SpikingMatrixClient::onAddMatrix, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); 
} 

, który wywołuje pierwszy zapis. Wywołanie zwrotne to

void SpikingMatrixClient::onAddMatrix(const boost::system::error_code& error, size_t bytes_transferred) { 
    std::cout << "entered onAddMatrix" << std::endl; 
    if (!error) { 
     buf.consume(bytes_transferred); 
     requestMatrixList(); 
    } else { 
     QString message = QString::fromStdString(error.message()); 
     this->ui->statusBar->showMessage(message, 15000); 
    } 
} 

Oddzwonienie nigdy nie jest wywoływane, mimo że serwer odbiera wszystkie dane. Czy ktokolwiek może pomyśleć o jakimkolwiek powodzie, dlaczego to robił?

P.S. Było opakowanie dla tego połączenia i tak, prawdopodobnie znowu będzie. Porzuciłem go dzień lub dwa temu, ponieważ nie mogłem znaleźć problemu z tym oddzwanianiem.

+3

Prawdopodobnie głupie pytanie, ale na wszelki wypadek ... Nazywasz "uruchom" na io_server, aby wysłać wywołania zwrotne? – jcoder

+1

Uzgodnione. Wygląda na to, że kod nie został napisany. Musi zobaczyć więcej do zdiagnozowania. – Chad

+0

@JohnB wcale nie głupie pytanie. Jak zauważył @Chad, nie ma nic złego w opublikowanym ograniczonym fragmencie kodu. @ TC1 Proponuję gotowanie tego problemu do odtwarzalnego przykładu, uzupełnionego o 'main' i' io_service.run() 'gdzieś. –

Odpowiedz

2

Zgodnie z sugestią, opublikowanie rozwiązania okazało się najbardziej odpowiednie (przynajmniej na razie).

Aplikacja klienta jest zapisywana w QT, a IO wymaga asynchronizacji. W przeważającej części klient otrzymuje dane obliczeniowe z aplikacji serwera i musi renderować różne ich reprezentacje graficzne.

Teraz istnieje kilka kluczowych aspektów do rozważenia:

  1. GUI musi być czuły, że nie powinny być blokowane przez IO.
  2. Klient może zostać podłączony/odłączony.
  3. Ruch jest dość intensywny, dane są wysyłane/odświeżane do klienta co kilka sekund i muszą pozostać aktywne (jak w punkcie 1).

Zgodnie z dokumentacją Boost.Asio,

Wiele wątków może zadzwonić io_service :: run() aby skonfigurować pulę wątków, z których koparki zakończenia może być wywoływany. Należy pamiętać, że wszystkie wątki, które dołączyły do ​​puli io_service, są uważane za równoważne, a io_service może dystrybuować pracę przez nie w dowolny sposób.

Zauważ, że io_service.run()bloki aż io_service zabraknie pracy.

Mając to na uwadze, przejrzystym rozwiązaniem jest uruchomienie io_service.run() z innego wątku.Odpowiednie fragmenty kodu są

void SpikingMatrixClient::connect() { 
    Ui::ConnectDialog ui; 
    QDialog *dialog = new QDialog; 
    ui.setupUi(dialog); 
    if (dialog->exec()) { 
     QString host = ui.lineEditHost->text(); 
     QString port = ui.lineEditPort->text(); 
     connection = TcpConnection::create(io); 
     boost::system::error_code error = connection->connect(host, port); 
     if (!error) { 
      io = boost::shared_ptr<boost::asio::io_service>(new boost::asio::io_service); 
      work = boost::shared_ptr<boost::asio::io_service::work>(new boost::asio::io_service::work(*io)); 
      io_threads.create_thread(boost::bind(&SpikingMatrixClient::runIo, this, io)); 
     } 
     QString message = QString::fromStdString(error.message()); 
     this->ui->statusBar->showMessage(message, 15000); 
    } 
} 

do podłączenia & rozpoczęciem IO, gdzie:

  • work jest prywatnym boost::shared_ptr do obiektu boost::asio::io_service::work został przekazany,
  • io jest prywatnym boost::shared_ptr do boost::asio::io_service ,
  • connection to boost::shared_ptr na moją klasę opakowania połączenia , a wywołanie connect() wykorzystuje resolwer itp. do podłączenia gniazda, istnieje wiele przykładów, że około
  • i io_threads jest prywatnym boost::thread_group.

Na pewno można go skrócić za pomocą niektórych maszyn w razie potrzeby.

TcpConnection to moja własna implementacja wrappera, której sortofom brakuje na razie funkcjonalności, i przypuszczam, że mógłbym przenieść do niej cały wątek, gdy zostanie przywrócony. Ten fragment powinien być wystarczający, aby dostać się pomysł tak ...

odłączenie część idzie tak:

void SpikingMatrixClient::disconnect() { 
    work.reset(); 
    io_threads.join_all(); 
    boost::system::error_code error = connection->disconnect(); 
    if (!error) { 
     connection.reset(); 
    } 
    QString message = QString::fromStdString(error.message()); 
    this->ui->statusBar->showMessage(message, 15000); 
} 
  • przedmiotem pracy jest zniszczony, tak że io_service może zabraknąć pracy w końcu,
  • nici są połączone, co oznacza, że ​​cała praca zostanie zakończona przed odłączeniem, więc dane nie powinny ulec uszkodzeniu,
  • zaproszeń disconnect()shutdown() i close() na gnieździe za t on sceny, a jeśli nie ma błędu, niszczy wskaźnik połączenia.

Zauważ, że nie ma obsługi błędów w przypadku błędu podczas odłączania w tym fragmencie, ale można to również zrobić, sprawdzając kod błędu (który wydaje się bardziej podobny do C) lub rzucając z disconnect(), jeśli kod błędu w nim znajduje się po próbie rozłączenia.

1

Napotkałem podobny problem (wywołania zwrotne nie zostały uruchomione), ale okoliczności różnią się od tego pytania (io_service miały zadania, ale nadal nie uruchamiały obsługi). W każdym razie opublikuję to i może to komuś pomoże.

W moim programie ustawiłem async_connect(), a następnie io_service.run(), który blokuje zgodnie z oczekiwaniami.

async_connect() przechodzi do on_connect_handler() zgodnie z oczekiwaniami, co z kolei odpala async_write().

on_write_complete_handler() nie uruchamia się, mimo że drugi koniec połączenia otrzymał wszystkie dane, a nawet wysłał odpowiedź.

Odkryłem, że jest to spowodowane przez umieszczenie logiki programu w on_connect_handler(). Konkretnie, po nawiązaniu połączenia i po wywołaniu async_write(), wprowadziłem nieskończoną pętlę, aby wykonać dowolną logikę, nie pozwalając na zakończenie operacji on_connect_handler().Zakładam, że powoduje to, że io_service nie może wykonywać innych procedur obsługi, nawet jeśli ich warunki są spełnione, ponieważ utknęły tutaj. (Miałem wiele nieporozumień i uważałem, że io_service automagicznie spawnuje wątki dla każdego połączenia). Mam nadzieję, że to pomoże.

Powiązane problemy