在 C++11 中对 boost::asio 套接字对象重复 std::move

Posted

技术标签:

【中文标题】在 C++11 中对 boost::asio 套接字对象重复 std::move【英文标题】:Repeated std::move on an boost::asio socket object in C++11 【发布时间】:2013-07-16 22:49:17 【问题描述】:

我正在探索使用 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

我的问题涉及server 类的tcp::socket 成员socket_。在server 类的do_accept() 方法中,socket_ 被传递给async_accept()。 (根据 asio 文档,async_accept() 需要 socket 作为其第一个参数来接受连接。)到目前为止,一切都很好。

下一个参数,异步接受操作的回调,是一个 lambda 函数。 lambda 的主体构造了一个新的session 对象,其构造函数也需要相同的socket。有趣的是,socket 对象不能被复制;所以在示例中,socket_ 对象是server 对象的成员,它使用std::move() 传递。

我了解“唯一”socket_ 对象(它是server 对象的“永久”成员)被“移动”到session 对象中。很好——socket 对象没有被复制,而是被移动了——每个人都很高兴。

但是在下一次调用async_accept() 时会发生什么?之前搬家的socket_server的成员)是不是又传进来了?当我们“移动”一个成员时,留下了什么?有无限socket对象的魔法喷泉吗?

或者这里发生的事情真的不太明显?当socket 移动到session 时,是“留下/移出”对象的内容(socket_ 的成员server交换与“新”session 对象自己的“尚未构建”socket_ 成员?我说得有道理吗?

总结

代码如下。程序流程相当简单。 main() 构造单个 server 对象。 server 反复调用async_accept()。每个async_accept() 回调创建一个新的session 对象,每个都用一个(新鲜的?)socket 构造。如果它们只是(重复地)从(单个)server 中的同一 socket_ 成员“移动”,那么所有“新鲜”socket 对象来自哪里?

#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;
 

【问题讨论】:

【参考方案1】:

tcp::socket reference 中所述:

在移动之后,被移动的对象处于相同的状态,就像 使用 basic_stream_socket(io_service&) 构造函数构造。

以上意味着您可以根据需要将原始socket 对象从serversession 多次move

【讨论】:

再想一想……这不是相当模糊的代码吗?这不会暴露应该更明确的隐藏功能吗? @Kevin H. Patterson 这里隐藏的功能在哪里?代码在我看来相当直观。另一种方法是(如在非 c++11 示例中)公开session::socket_,这是一种实现细节...... @Igor R. 好吧,我的理解是,使用移动语义,(源)右值应该处于准备好销毁的状态。这里(对我来说)不太明显的事情是,在我们移动它之后,右值将被重新初始化,就好像它是一个新构建的对象一样。当然代码是干净的,但知道这个结果是(恕我直言)session 构造函数的实现细节,不是吗?否则,我认为我们通常不应该在右值被移动到其他地方后尝试使用(或重新使用)它,对吧? @Kevin H. Patterson moveresult 是一个 rvlaue(或所谓的 xvalue),它“处于准备销毁的状态”。被移动的对象socket_ 是一个左值! @KevinH.Patterson 本页底部:thbecker.net/articles/rvalue_references/section_04.html 向我建议,移动的来源应该处于可破坏但已释放副作用资源的状态,这正是这里发生了什么。【参考方案2】:

移动语义可以被认为是传递资源的所有权。资源获取即实例化 (RAII) 是在对象构造时分配资源所有权并在销毁时释放这些资源的概念。移动语义允许在构建和销毁之外的其他时间转移资源的所有权。

在这种情况下,对象 (server::socket_) 是来自server::acceptor_ 的操作系统套接字资源所有权转移的接收者。该传输发生在async_accept() 返回后的某个时刻,此时客户端连接。新连接的socket资源被移入socket_,并调用回调lambda函数。在 lambda 期间,套接字资源被移动到 session::socket_。 Server::socket_ 只拥有资源的时间只有几分之一微秒。

移动语义允许 RAII 类以不拥有任何资源的暮光状态存在。在调用释放后想想unique_ptr(它指的是没有内存)。移出后的 server::socket_ 仍有空间容纳资源,但目前它一无所有。

lambda 函数做的最后一件事是调用do_accept,它再次调用async_accept()。传入了对socket_ 的引用。当另一个客户端在将来某个时间点连接时,async_accept() 将在那里转移新连接的 OS 套接字的所有权。

【讨论】:

感谢您详细说明该过程!抱歉很久没注意到你的回答。

以上是关于在 C++11 中对 boost::asio 套接字对象重复 std::move的主要内容,如果未能解决你的问题,请参考以下文章

C++ Boost.Asio - tcp 套接字异步写入

boost::asio 扩展 TCP 套接字

如何在 Boost.ASIO 中分配已连接的本机套接字类型 (TCP)

boost::asio::socket 线程安全

在 Boost.Asio 中同时使用 SSL 套接字和非 SSL 套接字?

两个线程c ++之间的boost asio通信