我正在探索使用 boost::asio 和 C++11 特性。特别是,我专注于一个名为“async_tcp_echo_server.cpp”的示例,位于此处(代码也显示在我的问题末尾):
http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp
我的问题涉及 tcp::socket
成员(member)socket_
的server
类(class)。在 do_accept()
server
的方法类(class),socket_
传递给 async_accept()
. (根据 asio 文档,async_accept()
需要作为其第一个参数的 socket
来接受连接。)到目前为止,一切都很好。
下一个参数,异步接受操作的回调,是一个 lambda 函数。 lambda 的主体构造了一个新的 session
对象,其构造函数也需要相同的 socket
.有趣的是,socket
对象不能被复制;所以在这个例子中,socket_
对象,它是 server
的成员对象,使用 std::move()
传递.
据我所知,“唯一”socket_
对象(它是 server
对象的“永久”成员)被“move ”到 session
中目的。很好 -- socket
对象不是复制,而是 move ——每个人都很高兴。
但是下次拨打 async_accept()
会发生什么? ?是不是一样socket_
(server
的成员),之前搬过的,又传进来了?当我们“move ”一个成员时,留下了什么?有没有无限魔力的喷泉socket
对象?
或者这里真的发生了一些不太明显的事情?当socket
移入 session
, 是“遗留/移出”对象( socket_
server
的成员)的内容与"new" session
的内容交换对象自己的“尚未构建”socket_
成员?我还有道理吗?
概括
代码如下。程序流程相当简单。 main()
构造单个 server
目的。 server
重复拨打 async_accept()
.每个async_accept()
回调创建一个新的 session
对象,每个对象都用一个(新鲜的?)socket
构造.哪里来的“新鲜”socket
对象来自,如果它们只是(重复)从同一个 socket_
“move ”成员(member)(单例)server
?
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class session
: public std::enable_shared_from_this<session>
{
public:
session( tcp::socket socket )
: socket_( std::move( socket ) )
{}
void start() {
do_read();
}
private:
void do_read() {
auto self( shared_from_this() );
socket_.async_read_some(
boost::asio::buffer( data_, max_length ),
[this, self]( boost::system::error_code ec, std::size_t length )
{
if( !ec ) {
do_write( length );
}
}
);
}
void do_write( std::size_t length ) {
auto self( shared_from_this() );
boost::asio::async_write(
socket_,
boost::asio::buffer( data_, length ),
[this, self]( boost::system::error_code ec, std::size_t /*length*/ )
{
if( !ec ) {
do_read();
}
}
);
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server {
public:
server( boost::asio::io_service& io_service, short port )
: acceptor_( io_service, tcp::endpoint( tcp::v4(), port ) )
, socket_( io_service )
{
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
socket_,
[this]( boost::system::error_code ec )
{
if( !ec ) {
std::make_shared<session>( std::move( socket_ ) )->start(); // is this a *swap* of socket_ ???
}
do_accept();
}
);
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
int main( int argc, char* argv[] ) {
try {
if( argc != 2 ) {
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
server s( io_service, std::atoi( argv[1] ) );
io_service.run();
} catch( std::exception& e ) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
请您参考如下方法:
move 语义可以被认为是传递资源的所有权。资源获取即实例化 (RAII) 是在对象构建时分配资源所有权并在销毁时释放这些资源的概念。 move 语义允许在除了构造和销毁之外的其他时间转移资源的所有权。
在这种情况下,对象( server::socket_
)是来自 server::acceptor_
的 OS 套接字资源所有权转移的接收者。 .该转移发生在 async_accept()
之后的某个时间点当客户端连接时返回。新连接的socket资源 move 到socket_
,并调用回调 lambda 函数。在 lambda 期间,套接字资源被 move 到 session::socket_
. Server::socket_ 仅在几微秒内拥有资源。
move 语义允许 RAII 类以不拥有任何资源的暮光状态存在。想一个 unique_ptr
在调用 release 之后(它指的是没有内存)。移出后的 server::socket_ 仍然有空间容纳资源,但目前它什么都不拥有。
lambda 函数所做的最后一件事是调用 do_accept
, 调用 async_accept()
再次。引用 socket_
传入。当另一个客户端在 future 某个时刻连接时,async_accept()
将在那里转移新连接的操作系统套接字的所有权。