async_connect在GNU/Linux下真的是异步的吗?

Is async_connect really asynchronous under GNU/Linux?

我想检查 Boost Asio 是否真的执行了异步连接。根据Asio's basics中发表的the diagrams corresponding to the asynchronous calls,当io_service向操作系统发出信号时开始运行,因此我理解在执行完async.connect指令后系统尝试执行该连接。

我的理解是,如果您不调用 run,您只会错过结果,但操作可能已经完成。所以我尝试用 nc -l -p 9000 创建一个虚拟服务器,然后使用下面附带的代码。

使用调试器,我一个语句一个语句地执行,并在 run 调用 io_service 之前停止。在服务器中,什么也没有发生。与连接无关——这很明显,因为虚拟服务器没有告诉你——也没有 async_write.

调用run函数后,服务器端会立即弹出相应的消息。我一直在 Boost 的 IRC 频道上询问,在展示了我的 strace 之后,一个非常聪明的人告诉我,这可能是因为在调用 run 之前套接字还没有准备好。显然,这不会发生在 Windows.

这是否意味着异步在 GNU/Linux 操作系统下并不是真正的异步?这是否意味着网站中显示的图表不对应 GNU/Linux 环境?

关于 "is not really asynchronous" 的注意事项:是的,它不会阻止调用,因此线程保持 运行 并做事,但我的意思是 异步 通过在操作执行后立即开始操作。

非常感谢您。

代码

#include <iostream>
#include <string.h>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

void connect_handler(const boost::system::error_code& error)
{
    if(error)
    {
        std::cout << error.message() << std::endl;
        exit(EXIT_FAILURE);
    }
    else
    {
        std::cout << "Successfully connected!" << std::endl;
    }
}

void write_handler(const boost::system::error_code& error)
{
    if(error)
    {
        std::cout << error.message() << std::endl;
        exit(EXIT_FAILURE);
    }
    else
    {
        std::cout << "Yes, we wrote!" << std::endl;
    }
}

int main()
{
    boost::asio::io_service io_service;
    boost::asio::ip::tcp::socket socket(io_service);
    boost::asio::ip::tcp::endpoint endpoint(
        boost::asio::ip::address::from_string("127.0.0.1"), 9000);

    socket.async_connect(endpoint, connect_handler);

    std::string hello_world("Hello World!");
    boost::asio::async_write(socket, boost::asio::buffer(hello_world.c_str(), 
        hello_world.size()), boost::bind(write_handler, 
                                boost::asio::placeholders::error));

    io_service.run();
    exit(EXIT_SUCCESS);
}

我的strace

futex(0x7f44cd0ca03c, FUTEX_WAKE_PRIVATE, 2147483647) = 0
futex(0x7f44cd0ca048, FUTEX_WAKE_PRIVATE, 2147483647) = 0
eventfd2(0, O_NONBLOCK|O_CLOEXEC)       = 3
epoll_create1(EPOLL_CLOEXEC)            = 4
timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC) = 5
epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLERR|EPOLLET, {u32=37842600, u64=37842600}}) = 0
write(3, "[=13=][=13=][=13=][=13=][=13=][=13=][=13=]", 8)         = 8
epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLERR, {u32=37842612, u64=37842612}}) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 6
epoll_ctl(4, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET, {u32=37842704, u64=37842704}}) = 0
ioctl(6, FIONBIO, [1])                  = 0
connect(6, {sa_family=AF_INET, sin_port=htons(9000), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
epoll_ctl(4, EPOLL_CTL_MOD, 6, {EPOLLIN|EPOLLPRI|EPOLLOUT|EPOLLERR|EPOLLHUP|EPOLLET, {u32=37842704, u64=37842704}}) = 0
epoll_wait(4, {{EPOLLIN, {u32=37842600, u64=37842600}}, {EPOLLOUT, {u32=37842704, u64=37842704}}}, 128, -1) = 2
poll([{fd=6, events=POLLOUT}], 1, 0)    = 1 ([{fd=6, revents=POLLOUT}])
getsockopt(6, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
sendmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"Hello World!", 12}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 12
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 5), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f44cd933000
write(1, "Successfully connected!\n", 24Successfully connected!
) = 24
epoll_wait(4, {}, 128, 0)               = 0
write(1, "Yes, we wrote!\n", 15Yes, we wrote!
)        = 15
exit_group(0)                           = ?
+++ exited with 0 +++

关于 boost.asio 的一个奇怪的事情 -- 不是它独有的,但通常不同于 OS-特定的异步网络框架 -- 是它不依赖于辅助线程。这有许多重要的后果,可以归结为:boost.asio 不适合在后台做事。相反,它用于在 前景 中做 多项 事情。

io_service::run() 是 boost.asio 的 "hub",使用 boost.asio 的单线程程序应该预计大部分时间都在 [=10= 中等待],或执行它调用的完成处理程序。根据特定于 OS 的内部实现,在调用该函数之前可能会或可能不会进行特定的异步操作 运行,这就是为什么调用它基本上是您在调用函数后要做的第一件事启动了您的初始异步请求。

async_connect 视为具有异步操作的 "arming" 您的 io_service。实际的异步发生在 io_service::run() 期间。事实上,调用 async_connect 后立即调用 async_write 是一件很奇怪的事情,我对它的工作原理感到有点惊讶。通常,你会从 connect_handler 中执行(或者更确切地说,"arm")async_write,因为只有在那个时候你才有一个连接的套接字。