boost :: asio tcp async_read никогда не возвращается - PullRequest
3 голосов
/ 09 марта 2010

Я пытаюсь преобразовать некоторый существующий код в сокеты asio tcp boost вместо нашей текущей реализации. Я могу получить очень похожий пример ( клиента / сервера чата ) с работающего сайта поддержки, но когда я пытаюсь вставить код в свою собственную программу, он перестает работать.

Что я делаю:

  1. Запустить процесс сервера
  2. Серверный процесс создает пустой сокет и использует его для прослушивания (с tcp :: acceptor) TCP-соединений через порт (например, 10010)
  3. Запустить клиентский процесс
  4. Пусть клиентский процесс создаст сокет для подключения к порту сервера
  5. Когда сервер видит, что клиент подключается, он начинает прослушивать данные (с async_read) в сокете и создает еще один пустой сокет для прослушивания другого TCP-соединения в порту
  6. Когда клиент видит, что сервер подключен, он отправляет 100 байтов данных (с async_write) и ждет, пока сокет сообщит, что передача завершена ... когда это произойдет, он напечатает сообщение и завершит работу
  7. Когда сервер получает уведомление о том, что у него есть данные, которые были прочитаны, он печатает сообщение и выключается

Очевидно, я значительно урезал этот код от того, что я пытаюсь реализовать, он настолько мал, насколько я мог бы создать что-то, что воспроизводит проблему. Я работаю на Windows и у меня есть файл решения Visual Studio, который вы можете получить . Есть некоторые утечки памяти, проблемы безопасности потоков и тому подобное, но это потому, что я убираю вещи из существующего кода, поэтому не беспокойтесь о них.

Во всяком случае, вот файлы заголовка с некоторыми общими вещами, сервером и клиентом.

Connection.hpp:


#ifndef CONNECTION_HPP
#define CONNECTION_HPP
#include 
#include 
#include 

class ConnectionTransfer
{
public:
   ConnectionTransfer(char* buffer, unsigned int size) :
      buffer_(buffer), size_(size)   {
   }
   virtual ~ConnectionTransfer(void){}

   char* GetBuffer(){return buffer_;}
   unsigned int GetSize(){return size_;}

   virtual void CallbackForFinished() = 0;

protected:
   char* buffer_;
   unsigned int size_;
};

class ConnectionTransferInProgress
{
public:
   ConnectionTransferInProgress(ConnectionTransfer* ct):
      ct_(ct)
   {}
   ~ConnectionTransferInProgress(void){}

   void operator()(const boost::system::error_code& error){Other(error);}
   void Other(const boost::system::error_code& error){
      if(!error)
         ct_->CallbackForFinished();
   }
private:
   ConnectionTransfer* ct_;
};

class Connection 
{
public:
   Connection(boost::asio::io_service& io_service):
   sock_(io_service)
   {}
   ~Connection(void){}
   void AsyncSend(ConnectionTransfer* ct){
      ConnectionTransferInProgress tip(ct);
      //sock_->async_send(boost::asio::buffer(ct->GetBuffer(), 
      //   static_cast(ct->GetSize())), tip);
      boost::asio::async_write(sock_, boost::asio::buffer(ct->GetBuffer(), 
         static_cast(ct->GetSize())), boost::bind(
         &ConnectionTransferInProgress::Other, tip, boost::asio::placeholders::error));
   }
   void AsyncReceive(ConnectionTransfer* ct){
      ConnectionTransferInProgress tip(ct);
      //sock_->async_receive(boost::asio::buffer(ct->GetBuffer(), 
      //   static_cast(ct->GetSize())), tip);
      boost::asio::async_read(sock_, boost::asio::buffer(ct->GetBuffer(), 
         static_cast(ct->GetSize())), boost::bind(
         &ConnectionTransferInProgress::Other, tip, boost::asio::placeholders::error));
   }

   boost::asio::ip::tcp::socket& GetSocket(){return sock_;}
private:
   boost::asio::ip::tcp::socket sock_;
};
#endif //CONNECTION_HPP

BoostConnectionClient.cpp:


#include "Connection.hpp"

#include 
#include 
#include 
#include 

using namespace boost::asio::ip;

bool connected;
bool gotTransfer; 

class FakeTransfer : public ConnectionTransfer
{
public:
   FakeTransfer(char* buffer, unsigned int size) : ConnectionTransfer(buffer, size)
   {
   }
   void CallbackForFinished()
   {
      gotTransfer = true;
   }
};

void ConnectHandler(const boost::system::error_code& error)
{
   if(!error)
      connected = true;
}

int main(int argc, char* argv[])
{
   connected = false;
   gotTransfer = false;

   boost::asio::io_service io_service;

   Connection* conn = new Connection(io_service);

   tcp::endpoint ep(address::from_string("127.0.0.1"), 10011);
   conn->GetSocket().async_connect(ep, ConnectHandler);

   boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));

   while(!connected)
   {
      boost::this_thread::sleep(boost::posix_time::millisec(1));
   }
   std::cout (angle brackets here) "Connected\n";

   char data[100];
   FakeTransfer* ft = new FakeTransfer(data, 100);
   conn->AsyncReceive(ft);

   while(!gotTransfer)
   {
      boost::this_thread::sleep(boost::posix_time::millisec(1));
   }

   std::cout (angle brackets here) "Done\n";

   return 0;
}

BoostConnectionServer.cpp:


#include "Connection.hpp"

#include 
#include 
#include 
#include 

using namespace boost::asio::ip;

Connection* conn1;
bool conn1Done;
bool gotTransfer;
Connection* conn2;

class FakeAcceptor
{
public:
   FakeAcceptor(boost::asio::io_service& io_service, const tcp::endpoint& endpoint)
      : 
      io_service_(io_service),
      acceptor_(io_service, endpoint)
  {
      conn1 = new Connection(io_service_);
      acceptor_.async_accept(conn1->GetSocket(),  
         boost::bind(&FakeAcceptor::HandleAccept, this, conn1, 
         boost::asio::placeholders::error));
   }
   void HandleAccept(Connection* conn, const boost::system::error_code& error)
   {
      if(conn == conn1)
         conn1Done = true;
      conn2 = new Connection(io_service_);
      acceptor_.async_accept(conn2->GetSocket(),  
         boost::bind(&FakeAcceptor::HandleAccept, this, conn2, 
         boost::asio::placeholders::error));
   }
   boost::asio::io_service& io_service_;
   tcp::acceptor acceptor_;
};

class FakeTransfer : public ConnectionTransfer
{
public:
   FakeTransfer(char* buffer, unsigned int size) : ConnectionTransfer(buffer, size)
   {
   }
   void CallbackForFinished()
   {
      gotTransfer = true;
   }
};

int main(int argc, char* argv[])
{
   boost::asio::io_service io_service;
   conn1Done = false;
   gotTransfer = false;
   tcp::endpoint endpoint(tcp::v4(), 10011);
   FakeAcceptor fa(io_service, endpoint);
   boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));

   while(!conn1Done)
   {
      boost::this_thread::sleep(boost::posix_time::millisec(1));
   }
   std::cout (angle brackets here) "Accepted incoming connection\n";

   char data[100];
   FakeTransfer* ft = new FakeTransfer(data, 100);
   conn1->AsyncReceive(ft);

   while(!gotTransfer)
   {
      boost::this_thread::sleep(boost::posix_time::millisec(1));
   }
   std::cout (angle brackets here) "Success!\n";
   return 0;
}

Я немного искал, но мне не повезло. Насколько я могу судить, я почти точно соответствую семплу, поэтому я должен пропустить что-то маленькое.

Спасибо!

1 Ответ

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

В вашем клиентском коде ваша функция обратного вызова ConnectHandler() просто устанавливает значение и затем возвращается, не отправляя больше работы io_service. В этот момент эта операция async_connect() является единственной работой, связанной с io_service; поэтому, когда ConnectHandler() возвращается, больше нет работы, связанной с io_service. Таким образом, вызов фонового потока в io_service.run() возвращается, и поток завершается.

Одним из возможных вариантов будет вызов conn->AsyncReceive() из ConnectHandler(), чтобы async_read() вызывался до возврата ConnectHandler() и, следовательно, вызов фонового потока io_service.run() не возвращался.

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

...
// some point in the main() method, prior to creating the background thread
boost::asio::io_service::work work(io_service)
...

Это задокументировано в документации io_service:

Прекращение работы io_service

Некоторым приложениям может потребоваться предотвратить возврат вызова run () объекта io_service, когда больше нет работы. Например, io_service может быть запущен в фоновом потоке, который запускается до асинхронных операций приложения. Вызов run () можно продолжать, создавая объект типа io_service :: work:

http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/reference/io_service.html

...