首页 > 解决方案 > 使用 C++ MJPEG 流服务器时缺少 boost::asio 随机标头

问题描述

我想我正面临一个 boost::asio 线程安全问题。让我解释:

语境:

我正在为 Ubuntu 14.04 使用带有 G++ 4.8.4 的 Boost 1.68

我正在编写 C++ 代码来制作 MJPEG 服务器。

这是我的一些代码示例,它的工作原理非常简单:

代码说明

(1) 填写HTTP头

(2) 以 HandleMJPEG 作为回调调用 doWrite

(3) doWrite 以 HandleMJPEG 作为回调调用 async_write

(4) HandleMJPEG:

(4.1) 将 --jpegBoundary 添加到流中

(4.2) 回调 doWrite 再次调用 HandleMJPEG (4.3)

实际代码

void Connection::main()
{
    streamInitReply(_reply); // (1)
    doWrite(
        std::bind(&net::Reply::toBuffers, _reply),
        std::bind(&Connection::handleMJPEG, this),
        "'MJPEG Stream init header'"); // (2)

}

void streamInitReply(net::Reply& rep)
{
    rep.status = net::Reply::Status::ok;
    rep.headers.clear();
    rep.content.clear();
    rep.headers.push_back(net::Header("Connection", "close"));
    rep.headers.push_back(net::Header("Max-Age", "0"));
    rep.headers.push_back(net::Header("Expires", "0"));
    rep.headers.push_back(net::Header("Cache-Control", "no-cache"));
    rep.headers.push_back(net::Header("Pragma", "no-cache"));

    rep.headers.push_back(
        net::Header(
            "Content-Type",
            (boost::format("multipart/x-mixed-replace; boundary=%1%") % jpegBoundary).str()
    )
);

void Connection::handleMJPEG()
{


    // Write Boundaries, then write the actual image buffer
    // then recall handleMJPEG() to loop and keep writing incoming image

    streamBoundariesReply(_reply);  // (4.1)
    auto self(shared_from_this());
    doWrite( // (4.2)
        std::bind(&net::Reply::contentToBuffers, _reply),
            [this, self](){
            imageReply(_server.getImage(), _reply); 
            doWrite(
                std::bind(&net::Reply::toBuffersWithoutStatus, _reply),
                std::bind(&Connection::handleMJPEG, this), // 4.3
                "'MJPEG Image'"
            );
        },
        "'MJPEG Boundary'"
    );
}

void Connection::doWrite(
    std::function<std::vector<boost::asio::const_buffer>()> toBuffer,
    std::function<void()> callbackOnSuccess,
    const std::string& logInfo)
{

    auto self(shared_from_this());

    boost::asio::async_write(
        _socket, 
        toBuffer(),
        boost::asio::bind_executor(strand_, [this, self, logInfo, callbackOnSuccess](const boost::system::error_code& ec, std::size_t)
        {

            if(ec != boost::system::errc::success)
            {
                doStop();
            }
            else
            {
                callbackOnSuccess(); // (3)
            }
        }
    ));
}

TL;博士

问题如下:这段代码在 90% 的时间里都能正常工作。有时,当我想在浏览器(Linux 的 Firefox 66.0.3)上查看流时,会出现“下载”弹出窗口,而不是显示图像。

当我调查这些包时,似乎有一些标题丢失或拼写错误:

1/ 当一切正常时

一切正常的时候在这里

2/ 当它不正常时

当它出错时在这里

或者这里也

这就是为什么怀疑 boost asio 中存在线程安全问题,但我不知道在哪里查看...

标签: c++boostthread-safetyboost-asiomjpeg

解决方案


你有悬空的参考。你的问题是std::bind

简介:当你有一个Foo具有成员函数的类时bar

class Foo {
  void bar() {}
};

Foo f;

然后你调用std::bind(&Foo::bar, f)std::bind返回一个函子,它存储f为 A COPY。

现在让我们返回您的代码:

doWrite(
    std::bind(&net::Reply::toBuffers, _reply),   // <---- [1]
    std::bind(&Connection::handleMJPEG, this),
    "'MJPEG Stream init header'");

在 [1] 中,您调用bindwhich 创建了存储_reply. 在doWrite

void Connection::doWrite(
    std::function<std::vector<boost::asio::const_buffer>()> toBuffer, // [2]
    std::function<void()> callbackOnSuccess,
    const std::string& logInfo)
{

    auto self(shared_from_this());

    boost::asio::async_write(
        _socket, 
        toBuffer(), // [3]
        boost::asio::bind_executor( ....
        ...
    ));
    // [4]
}

in [2]toBuffer是从 . 返回的函子的包装器std::bindtoBuffer是局部变量。在第 [3] 行中,您调用toBuffer()它返回 const-buffers 的副本_reply- 它不是指原始_replyasync_write立即返回 [4]。doWrite结束并被toBuffer销毁,而在Boost-lib 机制任务中的某个位置由async_writeworks启动,它正在访问从 - 返回的 const-buffers toBuffer- 在这里你有悬空的参考。

您可以_reply在调用时通过指针传递bind- 这样您将引用同一个_reply实例:

std::bind(&net::Reply::toBuffers, &_reply),

推荐阅读