libmicrohttpd的MHD_resume_connection()与外部选择无法正常工作

时间:2016-09-04 00:44:50

标签: c++

在使用外部事件循环时,我在 libmicrohttpd 中遇到了MHD_suspend_connection()MHD_resume_connection()的问题。之后我在下面写了一个小例子(没有错误处理)。我的问题是:我做错了什么?或者它是库中的错误?据我所知manual,它应该可以正常工作。明确允许使用带有暂停/恢复的外部选择。

问题是连接没有正确恢复。调用MHD_resume_connection()后,处理连接不会立即继续。在我的程序的某些版本中,它在另一个请求进入之后确实继续。在其他版本中,后来的请求根本没有处理(access_handler()从未被调用过)。在某些版本中,我在停止libmicrohttpd时收到第一个请求的响应。当我启用MHD_USE_SELECT_INTERNALLY并删除我的外部循环(让它睡眠)时,一切正常。

我在Debian(libmicrohttpd 0.9.37)和Arch(libmicrohttpd 0.9.50)上测试过它。两个系统都存在这个问题,但可能行为有点不同。

#include <algorithm>
#include <csignal>
#include <cstring>
#include <iostream>
#include <vector>

#include <sys/select.h>

#include <microhttpd.h>

using std::cerr;
using std::cout;
using std::endl;


static volatile bool run_loop = true;
static MHD_Daemon *ctx = nullptr;
static MHD_Response *response = nullptr;
static std::vector<MHD_Connection*> susspended;

void sighandler(int)
{
    run_loop = false;
}

int handle_access(void *cls, struct MHD_Connection *connection,
                  const char *url, const char *method, const char *version,
                  const char *upload_data, size_t *upload_data_size,
                  void **con_cls)
{
    static int second_call_marker;
    static int third_call_marker;
    if (*con_cls == nullptr) {
        cout << "New connection" << endl;
        *con_cls = &second_call_marker;
        return MHD_YES;
    } else if (*con_cls == &second_call_marker) {
        cout << "Suspending connection" << endl;
        MHD_suspend_connection(connection);
        susspended.push_back(connection);
        *con_cls = &third_call_marker;
        return MHD_YES;
    } else {
        cout << "Send response" << endl;
        return MHD_queue_response(connection, 200, response);
    }
}

void myapp()
{
    std::signal(SIGINT, &sighandler);
    std::signal(SIGINT, &sighandler);

    ctx = MHD_start_daemon(MHD_USE_DUAL_STACK //| MHD_USE_EPOLL
                           | MHD_USE_SUSPEND_RESUME | MHD_USE_DEBUG,
                           8080, nullptr, nullptr,
                           &handle_access, nullptr,
                           MHD_OPTION_END);
    response = MHD_create_response_from_buffer(4, const_cast<char*>("TEST"),
                                               MHD_RESPMEM_PERSISTENT);

    while (run_loop) {
        int max;
        fd_set rs, ws, es;
        struct timeval tv;
        struct timeval *tvp;

        max = 0;
        FD_ZERO(&rs);
        FD_ZERO(&ws);
        FD_ZERO(&es);

        cout << "Wait for IO activity" << endl;
        MHD_UNSIGNED_LONG_LONG mhd_timeout;
        MHD_get_fdset(ctx, &rs, &ws, &es, &max);
        if (MHD_get_timeout(ctx, &mhd_timeout) == MHD_YES) {
            //tv.tv_sec = std::min(mhd_timeout / 1000, 1ull);
            tv.tv_sec = mhd_timeout / 1000;
            tv.tv_usec = (mhd_timeout % 1000) * 1000;
            tvp = &tv;
        } else {
            //tv.tv_sec = 2;
            //tv.tv_usec = 0;
            //tvp = &tv;
            tvp = nullptr;
        }

        if (select(max + 1, &rs, &ws, &es, tvp) < 0 && errno != EINTR)
            throw "select() failed";

        cout << "Handle IO activity" << endl;
        if (MHD_run_from_select(ctx, &rs, &ws, &es) != MHD_YES)
            throw "MHD_run_from_select() failed";

        for (MHD_Connection *connection : susspended) {
            cout << "Resume connection" << endl;
            MHD_resume_connection(connection);
        }
        susspended.clear();
    }

    cout << "Stop server" << endl;
    MHD_stop_daemon(ctx);
}

int main(int argc, char *argv[])
{
    try {
        myapp();
    } catch (const char *str) {
        cerr << "Error: " << str << endl;
        cerr << "Errno: " << errno << " (" << strerror(errno) << ")" << endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

1 个答案:

答案 0 :(得分:3)

我已在Windows上编译并运行您的示例,并且看到了与0.9.51相同的行为。

这不是microhttpd中的错误。问题是您在排队响应之前恢复连接。您创建响应的唯一代码依赖于连接上的更多活动,因此它是一个捕获22。

MHD_suspend_connection / MHD_resume_connection的目的是在长时间运行的工作中不阻止新连接。因此,通常在挂起连接之后,您需要在另一个线程上启动该工作以在维护侦听套接字时继续。当该线程对响应进行排队时,它可以恢复连接,事件循环将知道它已准备好发送回客户端。

我不确定您的其他设计要求,但您可能不需要实施外部选择。也就是说,暂停/恢复不需要它(我使用暂停/恢复就好MHD_USE_SELECT_INTERNALLY,例如。)。