【问题标题】:Passing RAII socket class to thread将 RAII 套接字类传递给线程
【发布时间】:2021-09-15 19:50:25
【问题描述】:

我正在制作一个 HTTP 服务器,我想将一个套接字客户端类传递给线程来处理请求。问题是在启动线程后立即调用析构函数,因此套接字被关闭。 简化版的代码如下所示。

while (true) {
    TcpSocket client = m_socket.accept();
    std::thread clientThread([this, &client] { this->handleClient(client); });
    clientThread.detach();
}

我尝试创建一个接受连接并将其传递给具有给定函数的线程的函数,但我无法让它工作。我在这里使用元组是因为你 can't pass a parameter pack to lambda in c++17。

template<typename Function, typename ...Args>
std::thread TcpListener::acceptAndPassToThread(Function&& function, Args&&... args)
{
    int socketDescriptor = accept(m_socketDescriptor, nullptr, nullptr);

    std::tuple arguments = std::make_tuple(std::forward<Args>(args)...);

    std::thread clientThread([socketDescriptor, function, arguments] {
        TcpSocket client = TcpSocket(socketDescriptor);

        auto callArguments = std::tuple_cat(std::make_tuple(client), arguments);
        std::apply(function, callArguments);

    });

    return clientThread;
}

我可以做这样的事情,但我不想在类之外使用套接字描述符。

while (true)
{
    int clientDescriptor = m_socket.acceptDescriptor();

    std::thread clientThread([this, clientDescriptor] {
        TcpSocket client = TcpSocket::fromDescriptor(clientDescriptor);
        this->handleClient(client);
    });

    clientThread.detach();
}

还有什么是使用类成员函数调用 TcpListener::acceptAndPassToThread 函数的正确方法。

【问题讨论】:

    标签: c++ multithreading sockets variadic-templates raii


    【解决方案1】:

    你的RAII 类TcpSocket 真的应该只移动,因为复制在逻辑上是错误的。当然,您可以像 std::shared_ptr 那样实现它,但是值语义有点误导。那么,将其称为反映其共享性质的东西可能会更好。

    要让一个类只移动你删除复制构造函数和复制赋值运算符并提供move 两个版本。像这样的:

    class TcpSocket
    {
    public:
        // ...
    
        ~TcpSocket() { this->close(); }
    
        TcpSocket(int descriptor = -1): descriptor(descriptor) {}
      
        // ban copying  
        TcpSocket(TcpSocket const&) = delete;
        TcpSocket& operator=(TcpSocket const&) = delete;
    
        // implement move so the moved from object will
        // not close this descriptor    
        TcpSocket(TcpSocket&& other): TcpSocket()
        {
            std::swap(descriptor, other.descriptor);
        }
    
        // same with move assignment
        // this closes any current descriptor but you may
        // want to throw an exception if this descriptor
        // is currently in use.    
        TcpSocket& operator=(TcpSocket&& other)
        {
            if(&other != this)
                this->close(); // or throw?
    
            std::swap(descriptor, other.descriptor);
    
            return *this;
        }
    
    private:
        void close()
        {
            if(descriptor != -1)
                ::close(descriptor);
            descriptor = -1;
        }
    
        int descriptor = -1;
    };
    

    然后移动套接字进入线程:

    while (true) {
        TcpSocket client = m_socket.accept();
        std::thread clientThread([this, client = std::move(client)] mutable {
            this->handleClient(std::move(client));
        });
    
        clientThread.detach(); // looks dangerout?
    }
    

    【讨论】:

    • 感谢您的回答。我尝试实现它,但在第二步我得到一个错误“void HttpServer::handleClient(TcpSocket &)': cannot convert argument 1 from 'const TcpSocket' to 'TcpSocket &&”。我不知道为什么对象是 const。还有“TcpSocket const&”是什么意思。
    • @Wojak2121 Soz,代码未经测试。可能 llambda 需要是 mutable (llambdas 默认是 const )。我修改了答案以进行演示。 BTW TcpSocket const&amp;和const TcpSocket&amp;一样
    【解决方案2】:

    如果您可以控制 TcpSocket 类,我建议您通过将 private implementation 成员包装在 shared_ptr(或 unique_ptr 如果您使用它)中来实现 pimpl 习惯用法对)。

    要解决您当前的问题,您可以使用 std::shared_ptr&lt;TcpSocket&gt; 而不是原始类,如下所示:

    while (true) {
        std::shared_ptr<TcpSocket> client = m_socket.accept();
        std::thread clientThread([this, client] { this->handleClient(client); });
        clientThread.detach();
    }
    
    void handleClient(std::shared_ptr<TcpSocket> client) {
        // ...
    }
    

    您必须更改函数 m_socket.accept() 才能返回 shared_ptr。

    【讨论】:

    • TcpSocket 使用动态分配的理由为 0,使用 shared_ptr 的理由更少。您需要将界面与在呼叫站点上使用它的方式紧密耦合的简单事实应该已经是一个主要的危险信号。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-03
    • 2019-09-04
    • 2013-12-25
    • 2019-07-04
    • 1970-01-01
    • 1970-01-01
    • 2013-04-09
    相关资源
    最近更新 更多