случайный заголовок boost :: asio отсутствует при использовании потокового сервера C ++ MJPEG - PullRequest
0 голосов
/ 03 октября 2019

Я думаю, что столкнулся с проблемой повышения безопасности ASIO. Позвольте мне объяснить:

Контекст:

Я использую Boost 1.68 с G ++ 4.8.4 для Ubuntu 14.04

Я пишу код C ++ для создания сервера MJPEG.

Вот несколько примеров моего кода, его работа довольно проста:

Пояснения к коду

(1) Заполнение HTTP-заголовка

(2) Вызов doWrite с HandleMJPEG в качестве обратного вызова

(3) DoWrite вызывает async_write с HandleMJPEG в качестве обратного вызова

(4) HandleMJPEG:

(4.1) добавляет--jpegBoundary to stream

(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 для Linux), вместо отображения изображений появляется всплывающее окно «download».

Когда я исследую пакеты, кажется, что естькакой-то заголовок отсутствует или написан с ошибкой:

1 / когда все в порядке

here when everything is OK

2 / когда все не в порядке

here when it goes wrong

or here also

Именно поэтому есть подозрение на проблему с поддержкой потоков в boost asio, но я не знаю, гдесмотреть на ...

1 Ответ

1 голос
/ 03 октября 2019

У вас есть свисающая ссылка. Ваша проблема std::bind.

Краткое введение: когда у вас есть класс Foo, который имеет функцию-член bar:

class Foo {
  void bar() {}
};

Foo f;

и вы вызываете std::bind(&Foo::bar, f), std::bind возвращает вам функтор, который хранит f как КОПИЮ.

Теперь давайте вернемся к вашему коду:

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

в [2] toBuffer - обертка для функтора, возвращенная из std::bind. toBuffer является локальной переменной. В строке [3] вы вызываете toBuffer(), который возвращает const-buffers для копии _reply - это не относится к оригиналу _reply! async_write немедленно возвращается [4]. doWrite заканчивается и toBuffer уничтожается, тогда как где-то в механизме Boost-lib задача, инициированная async_write, работает и получает доступ к const-буферам, возвращенным из toBuffer - и здесь у вас есть свисающая ссылка.

Вы можете передать _reply указателем при вызове bind - таким образом, вы будете ссылаться на один и тот же _reply экземпляр:

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