简体   繁体   English

boost :: asio http客户端停止工作,我不知道为什么

[英]boost::asio http client stops doing work and I don't know why

I have a program that uses boost::asio to assign work to several threads. 我有一个使用boost :: asio将工作分配给多个线程的程序。 The work consists of starting a http client, making a request and storing the answer in a file. 这项工作包括启动http客户端,发出请求并将答案存储在文件中。 Sometimes there is a bug which causes the program to never finish and stop writing any output. 有时,有一个错误会导致程序永不结束并停止编写任何输出。 I've been unable to figure out what exactly is going wrong, as the program doesn't report any errors or problems that could explain this behavior (it does report the occasional timeout or some other minor issue). 我一直无法弄清楚到底是哪里出了问题,因为该程序没有报告任何可以解释这种现象的错误或问题(它确实报告了偶发的超时或其他一些小问题)。

I'm on Windows and typing "netstat -n" into the console shows that the program maintains 8 established connections with the target host even long after it has stopped doing work (one connection for each thread). 我在Windows上,在控制台中键入“ netstat -n”表明,该程序甚至在停止工作很长时间之后仍与目标主机保持8条已建立的连接(每个线程一个连接)。

Mutexes that are used: 使用的互斥锁:

std::mutex catch_mx, result_mx, debug_mx;

Assigning work to threads: 将工作分配给线程:

    boost::asio::io_service io_service;
    for (auto &wordset : wordsets)
    for (auto &unicode_string : wordset.variants)
        io_service.post(std::bind(send_query, std::ref(io_service), std::ref(unicode_string)));

    std::vector<std::thread> threads;
    threads.reserve(std::max(1u, std::thread::hardware_concurrency()));
    for (auto i = 0u; i < threads.capacity(); ++i)
        threads.emplace_back(thread_function, std::ref(io_service));

    for (auto &t : threads)
        t.join();

Allow the thread to receive work: 允许线程接收工作:

void thread_function(boost::asio::io_service &io_service)
{
    io_service.run();
}

The function that makes the http request and interprets the response. 发出http请求并解释响应的函数。 The http client code has been copied from the boost::asio synchronous http client example. http客户端代码已从boost :: asio同步http客户端示例复制而来。 The only difference is in the error handling and the response being written to a file rather than std::cout 唯一的区别是错误处理和响应被写入文件而不是std::cout

void send_query(boost::asio::io_service &io_service, const Ustring &unicode_string)
{
    try
    {
        using boost::asio::ip::tcp;
        auto query_string = generate_query(unicode_string);
        debug_log(unicode_string, query_string);
        tcp::resolver resolver(io_service);
        tcp::resolver::query query("somehost.com", "http");
        tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
        tcp::socket socket(io_service);
        boost::asio::connect(socket, endpoint_iterator);
        boost::asio::streambuf request;
        std::ostream request_stream(&request);
        request_stream << "GET " << "somepath" + query_string << " HTTP/1.0\r\n";
        request_stream << "Host: " << "somehost.com" << "\r\n";
        request_stream << "Accept: */*\r\n";
        request_stream << "Connection: close\r\n\r\n";
        boost::asio::write(socket, request);
        boost::asio::streambuf response;
        boost::asio::read_until(socket, response, "\r\n");
        std::istream response_stream(&response);
        std::string http_version;
        response_stream >> http_version;
        unsigned int status_code;
        response_stream >> status_code;
        std::string status_message;
        std::getline(response_stream, status_message);
        if (!response_stream || http_version.substr(0, 5) != "HTTP/")
            throw AUTO_EXCEPTION("invalid response");
        if (status_code != 200) // for now, consider this an error
            throw AUTO_EXCEPTION("response status code " + std::to_string(status_code));

        boost::asio::read_until(socket, response, "\r\n\r\n");
        std::stringstream ss;
        std::string header;
        while (std::getline(response_stream, header) && header != "\r");
        ss << header << "\n";
        ss << "\n";

        if (response.size() > 0)
            ss << &response;

        boost::system::error_code error;
        while (boost::asio::read(socket, response, boost::asio::transfer_at_least(1), error))
            ss << &response;
        if (error != boost::asio::error::eof)
            throw AUTO_EXCEPTION(error.message());

        write_result(ss.str());
    }
    catch (const std::exception &e)
    {

        std::unique_lock<std::mutex> lock(catch_mx);
        std::ofstream ofs("error.log", std::ios_base::app);
        ofs << "Thread " << std::this_thread::get_id() << ": " << e.what() << std::endl;
        ofs.close();
    }
}

Logging functions 记录功能

void debug_log(const Ustring &code_points, std::string &query)
{
    std::unique_lock<std::mutex> lock(debug_mx);
    std::ofstream ofs("debug.log", std::ios_base::app);
    ofs << unicode_to_string(code_points) << " " << query << std::endl;
    ofs.close();
}

void write_result(const std::string &s)
{
    std::unique_lock<std::mutex> lock(result_mx);
    std::ofstream ofs("results.txt", std::ios_base::app);
    ofs << s << std::endl;
    ofs.close();
}

PS: doing as AndyT suggested, I found that the threads all seem to get stuck at the same step in one of boost::asio's functions (in socket_ops.ipp): PS:按照AndyT的建议,我发现在boost :: asio的函数之一(在socket_ops.ipp中)中,所有线程似乎都陷入了同一步:

signed_size_type recv(socket_type s, buf* bufs, size_t count,
    int flags, boost::system::error_code& ec)
{
  clear_last_error();
#if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
  // Receive some data.
  DWORD recv_buf_count = static_cast<DWORD>(count);
  DWORD bytes_transferred = 0;
  DWORD recv_flags = flags;
  int result = error_wrapper(::WSARecv(s, bufs,
        recv_buf_count, &bytes_transferred, &recv_flags, 0, 0), ec); // this is where they all get stuck
  if (ec.value() == ERROR_NETNAME_DELETED)
    ec = boost::asio::error::connection_reset;
  else if (ec.value() == ERROR_PORT_UNREACHABLE)
    ec = boost::asio::error::connection_refused;
  if (result != 0)
    return socket_error_retval;
  ec = boost::system::error_code();
  return bytes_transferred;
#else // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
  msghdr msg = msghdr();
  msg.msg_iov = bufs;
  msg.msg_iovlen = static_cast<int>(count);
  signed_size_type result = error_wrapper(::recvmsg(s, &msg, flags), ec);
  if (result >= 0)
    ec = boost::system::error_code();
  return result;
#endif // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
}

This looks like deadlock. 这看起来像死锁。 It is common problem for asynchronous code that use locks. 使用锁的异步代码是一个常见问题。 Try to use strands instead of locks in your boost::asio code. 尝试在您的boost :: asio代码中使用链而不是锁。 You can post handlers to your io_service and wrap them with different strands. 您可以将处理程序发布到io_service上,并用不同的链来包装它们。 One strand for debug, one for writing output and one for handling errors. 一串用于调试,一串用于写入输出,一串用于处理错误。 For example, when you need to write debug information - you need to create handler that do it, than wrap it with corresponding strand and than - post it to io_service. 例如,当您需要编写调试信息时-您需要创建执行该操作的处理程序,而不是用相应的链将其包装,然后-将其发布到io_service中。

It is better to use asynchronous operations for all I/O. 最好对所有I / O使用异步操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM