boost::asio 使用 C++ MJPEG 流媒体服务器时缺少随机 header

boost::asio random header missing when using a C++ MJPEG streaming server

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

上下文:

我将 Boost 1.68 与 G++ 4.8.4 一起用于 Ubuntu 14.04

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

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

代码说明

(1) Filling-in HTTP header

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

(3) doWrite 调用 async_write 并使用 HandleMJPEG 作为回调

(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;DR

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

当我调查包裹时,似乎有一些 header 遗漏或拼写错误:

1/ 当一切正常时

2/ 不行的时候

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

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

简介:当你有一个class Foo,它的成员函数是bar:

class Foo {
  void bar() {}
};

Foo f;

你调用 std::bind(&Foo::bar, f)std::bind return 你是一个将 f 存储为副本的仿函数。

现在 return 你的代码:

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

在 [1] 中调用 bind,它创建存储 _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::bind 编辑的函子 return 的包装。 toBuffer 是局部变量。在第 [3] 行中,您调用 toBuffer(),其中 returns const-buffers 用于 _reply 的副本 - 它不引用原始 _replyasync_write return 立即 [4]。 doWrite 结束并且 toBuffer 被销毁,而 Boost-lib 机制 中的某处由 async_write 启动的任务工作并且它正在访问常量缓冲区 return来自 toBuffer - 这里有悬空参考。

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

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