async_connect不会在TCP客户端类

时间:2017-04-02 20:10:10

标签: c++ boost tcp client boost-asio

我试图从我的项目的boost TCP客户端示例中创建一个客户端类,并且我注意到有时在连接到不存在的主机时不会调用handle_connect。

我已经在堆栈上阅读了类似的问题,人们忘记在发布任务之前忘记运行io_service或调用它,但我不认为这是我的情况,因为我启动了io_service .run()线程在调用async_connect之后,成功连接,网络无法访问,以及其他一些我测试过的工作正常。

以下是完整列表:

tcp_client.hpp

#ifndef TCP_CLIENT_HPP
#define TCP_CLIENT_HPP

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <mutex>
#include <iostream>
#include <iomanip>

namespace com {

using boost::asio::ip::tcp;
using namespace std;

class client : public boost::enable_shared_from_this<client> {

private:

  std::mutex mx_;
  bool stopped_ = 1;
  boost::asio::streambuf ibuf_;
  boost::shared_ptr<boost::asio::io_service> io_service_;
  boost::shared_ptr<boost::asio::ip::tcp::socket> sock_;
  boost::shared_ptr<tcp::resolver::iterator> ei_;
  std::vector<std::string> inbound_;
  std::string host_, port_;

public:

  client() {}

  void connect( std::string host, std::string port ) {
    if (!stopped_) stop();
    host_ = host; port_ = port;
    io_service_.reset(new boost::asio::io_service);
    sock_.reset(new boost::asio::ip::tcp::socket(*io_service_));
    ei_.reset(new tcp::resolver::iterator);
    tcp::resolver r(*io_service_);
    ei_ = boost::make_shared<tcp::resolver::iterator>( r.resolve(tcp::resolver::query(host_, port_)) );
    stopped_ = 0;
    start_connect();
    boost::thread work( boost::bind(&client::work, shared_from_this()) );
    return;
  }

  bool is_running() {
    return !stopped_;
  }

  void stop() {
    stopped_ = 1;
    sock_->close();
    return;
  }

  void send(std::string str) {
    if (stopped_) return;
    auto msg = boost::asio::buffer(str, str.size());
    boost::asio::async_write( (*sock_), msg, boost::bind(&client::handle_write, shared_from_this(), _1) );
    return;
  }

  std::string pull() {
    std::lock_guard<std::mutex> lock(mx_);
    std::string msg;
    if (inbound_.size()>0) {
      msg = inbound_.at(0);
      inbound_.erase(inbound_.begin());
    }
    return msg;
  }

  int size() {
    std::lock_guard<std::mutex> lock(mx_);
    return inbound_.size();
  }

  void clear() {
    std::lock_guard<std::mutex> lock(mx_);
    inbound_.clear();
    return;
  }

private:

  void work() {
    if (stopped_) return;
    std::cout<<"work in"<<std::endl;
    io_service_->run();
    std::cout<<"work out"<<std::endl;
    return;
  }

  void start_connect() {
    if ((*ei_) != tcp::resolver::iterator()) {
      std::cout<<"Trying "<<(*ei_)->endpoint()<<std::endl;
      sock_->async_connect( (*ei_)->endpoint(), boost::bind(&client::handle_connect, shared_from_this(), boost::asio::placeholders::error) );
    } else {
      stop();
    }
    return;
  }

  void handle_connect(const boost::system::error_code& ec) {
    if (stopped_) return;

    if (!sock_->is_open()) {
      std::cout<<"Socket closed"<<std::endl;
      (*ei_)++;
      start_connect();
    } else if (ec) {
      std::cout<<"Connect error: "<<ec.message()<<std::endl;
      sock_->close();
      (*ei_)++;
      start_connect();
    } else {
      std::cout<<"Connected to "<<(*ei_)->endpoint()<<std::endl;
      start_read();
    }

    return;
  }

  void start_read() {
    if (stopped_) return;
    boost::asio::async_read_until((*sock_), ibuf_, "", boost::bind(&client::handle_read, shared_from_this(), boost::asio::placeholders::error));
    return;
  }

  void handle_read(const boost::system::error_code& ec) {
    std::lock_guard<std::mutex> lock(mx_);
    if (stopped_) return;
    if (ec) {
      std::cout<<"Read error: "<<ec.message()<<std::endl;
      stop();
      return;
    }

    std::string line;
    std::istream is(&ibuf_);
    std::getline(is, line);
    if (!line.empty() && inbound_.size()<1000) inbound_.push_back(line);

    start_read();
    return;
  }

private:

  void handle_write(const boost::system::error_code& ec) {
    if (stopped_) return;
    if (ec) {
      std::cout<<"Write error: "<<ec.message()<<std::endl;
      stop();
      return;
    }
    return;
  }

};

};

tcp_test.cpp

#include "tcp_client.hpp"

int main(int argc, char* argv[]) {
  auto tcp_client = boost::shared_ptr<com::client>(new com::client);

  try {
    tcp_client->connect("192.168.1.15", "50000");
    boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
    tcp_client->connect("192.168.1.20", "50000");
  } catch (std::exception& e) {
    std::cerr<<"Exception: "<<e.what()<<std::endl;
  }

  int cnt=0;
  while (cnt<5) {
    std::cout<<cnt<<std::endl;
    cnt++;
    tcp_client->send("<test>");
    boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
  }

  tcp_client->stop();

  while (tcp_client->size()>0) std::cout<<tcp_client->pull()<<std::endl;

  return 0;
}

我得到的输出是连接到环回服务器时:

Trying 192.168.1.15:50000
work in
work out
Trying 192.168.1.20:50000
0
work in
Connected to 192.168.1.20:50000
1
2
3
4
work out
<test>
<test>
<test>
<test>
<test>

正如你所看到的,192.168.1.20可以正常工作。 192.168.1.15并不存在,但我预计它会引发某种错误。而是io_service.run()立即返回,就像async_connect从未发布回调任务一样。也许它与端点迭代器相关而不是async_connect?

任何人都可以解释为什么会这样吗?

然后我试图在此代码中找出问题:

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/thread.hpp>

boost::asio::io_service io_svc;
boost::asio::ip::tcp::socket sock(io_svc);
boost::asio::ip::tcp::resolver::iterator ei;

void work() {
  std::cout<<"work in"<<std::endl;
  io_svc.run();
  std::cout<<"work out"<<std::endl;
  return;
}

void stop() {
  sock.close();
  return;
}

void start_connect();

void handle_connect(const boost::system::error_code& ec) {
  if (!sock.is_open()) {
    std::cout<<"Socket closed"<<std::endl;
    ei++;
    start_connect();
  } else if (ec) {
    std::cout<<"Connect error: "<<ec.message()<<std::endl;
    sock.close();
    ei++;
    start_connect();
  } else {
    std::cout<<"Connected to "<<ei->endpoint()<<std::endl;
  }
  return;
}

void start_connect() {
  if (ei != boost::asio::ip::tcp::resolver::iterator()) {
    std::cout<<"Trying "<<ei->endpoint()<<std::endl;
    sock.async_connect( ei->endpoint(), boost::bind(handle_connect, boost::asio::placeholders::error) );
  } else {
    stop();
  }
  return;
}

int main(int argc, char* argv[]) {

  std::string host="192.168.1.15", port="50000";

  boost::asio::ip::tcp::resolver r(io_svc);
  ei = r.resolve(boost::asio::ip::tcp::resolver::query(host, port));
  start_connect();
  boost::thread* thr = new boost::thread(work);

  boost::this_thread::sleep_for(boost::chrono::milliseconds(2000));

  return 0;
}

但是我得到了完全不同的结果。当我尝试连接到不存在的主机时,大部分时间它都是:

Trying 192.168.1.15:50000
work in

有时它是:

Trying 192.168.1.15:50000
work in
Connect error: Operation canceled
Connect error: Operation canceled

很少发生:

Trying 192.168.1.15:50000
work in
Segmentation fault

&#34;锻炼&#34;从来没有打印过,所以我猜这个例子中的io_service正在做一些事情,但是这与以前的代码有什么不同,以及为什么我得到&#34;操作被取消&#34;有时只出错?

1 个答案:

答案 0 :(得分:0)

在后台线程中运行的客户端看起来应该是这样的。

请注意,我注意到包括连接超时等内容。为此,您需要一个与async_connect并行运行的截止时间计时器。然后你必须正确处理交叉情况(提示:在成功连接时取消截止时间计时器并从其async_wait中丢弃随后的错误)。

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <thread>
#include <functional>

boost::asio::io_service io_svc;

struct client
    : std::enable_shared_from_this<client>
{
    using protocol = boost::asio::ip::tcp;
    using resolver = protocol::resolver;
    using socket = protocol::socket;

    using error_code = boost::system::error_code;

    client(boost::asio::io_service& ios)
        : ios_(ios) {}

    void start(std::string const& host, std::string const& service)
    {
        auto presolver = std::make_shared<resolver>(get_io_service());

        presolver->async_resolve(protocol::resolver::query(host, service),
                                 strand_.wrap([self = shared_from_this(), presolver](auto&& ec, auto iter)
                                              {
                                                  self->handle_resolve(ec, presolver, iter);
                                              }));

    }

private:
    void
    handle_resolve(boost::system::error_code const& ec, std::shared_ptr<resolver> presolver, resolver::iterator iter)
    {
        if (ec) {
            std::cerr << "error resolving: " << ec.message() << std::endl;
        }
        else {
            boost::asio::async_connect(sock, iter, strand_.wrap([self = shared_from_this(),
                                                                    presolver]
                                                                    (auto&& ec, auto iter)
                                                                {
                                                                    self->handle_connect(ec, iter);
                                                                    // note - we're dropping presolver here - we don't need it any more
                                                                }));
        }
    }

    void handle_connect(error_code const& ec, resolver::iterator iter)
    {
        if (ec) {
            std::cerr << "failed to connect: " << ec.message() << std::endl;
        }
        else {
            auto payload = std::make_shared<std::string>("Hello");

            boost::asio::async_write(sock, boost::asio::buffer(*payload),
                                     strand_.wrap([self = shared_from_this(),
                                                      payload] // note! capture the payload so it continues to exist during async send
                                                      (auto&& ec, auto size)
                                                  {
                                                      self->handle_send(ec, size);
                                                  }));
        }
    }

    void handle_send(error_code const& ec, std::size_t size)
    {
        if (ec) {
            std::cerr << "send failed after " << size << " butes : " << ec.message() << std::endl;
        }
        else {
            // send something else?
        }
    }

    boost::asio::io_service& get_io_service()
    {
        return ios_;
    }

private:

    boost::asio::io_service& ios_;
    boost::asio::strand strand_{get_io_service()};
    socket              sock{get_io_service()};

};

void work()
{
    std::cout << "work in" << std::endl;
    io_svc.run();
    std::cout << "work out" << std::endl;
    return;
}

int main(int argc, char *argv[])
{

    auto        pclient = std::make_shared<client>(io_svc);
    std::string host    = "192.168.1.15", port = "50000";
    pclient->start(host, port);

    auto run_thread = std::thread(work);
    if (run_thread.joinable())
        run_thread.join();

    return 0;
}

示例输出:

work in
    <time passes>...
failed to connect: Operation timed out
work out