将 RAII 套接字 class 传递给线程

Passing RAII socket class to thread

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

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

我尝试制作一个接受连接并将其传递给具有给定函数的线程的函数,但我无法让它工作。我在这里使用元组是因为你 .

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

我可以做这样的事情,但我不想在 class.

之外使用套接字描述符
while (true)
{
    int clientDescriptor = m_socket.acceptDescriptor();

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

    clientThread.detach();
}

另外,使用 class 成员函数调用 TcpListener::acceptAndPassToThread 函数的正确方法是什么。

如果您可以控制 TcpSocket class,我建议您通过将 私有实现 成员包装在 shared_ptr(或 unique_ptr 如果你使用得当)。

要解决您眼前的问题,您可以只使用 std::shared_ptr<TcpSocket> 而不是原始的 class,如下所示:

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() 才能 return 成为 shared_ptr

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

要使class 仅移动,您删除 复制构造函数copy assignment operator 并提供两者的 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?
}