发布多部分文件上传时的 libevent 块

Posted

技术标签:

【中文标题】发布多部分文件上传时的 libevent 块【英文标题】:libevent blocks when posting multipart file upload 【发布时间】:2018-10-16 14:52:08 【问题描述】:

我正在使用 linux 内核 4.14 使用 C 语言开发嵌入式 arm 板。 我正在使用 libevent 版本 2,并为两个 URL 创建了处理程序。 一个用于发布文件,另一个用于获取上传状态。 用户将通过浏览器连接并通过多部分表单 POST 上传文件,并使用 GET 请求获取上传状态。

这里有一些伪代码,因此您可以了解该过程。

void upload_cb(struct evhttp_request *req, void *arg)
    struct bufferevent *bev = evhttp_connection_get_bufferevent(req->evcon);
    if (bev) //Prio is initialized with 10 states in main
        bufferevent_priority_set(bev, 9); // set to low priority
    struct evbuffer* post_buffer = evhttp_request_get_input_buffer(req);
    size_t body_size = evbuffer_get_length(post_buffer);
    // a multipart parser takes care of writing the 
    // post_buffer content to a file
    // this takes a few seconds and after completing this 
    // the status_cb is accessible again


void status_cb(struct evhttp_request *req, void *arg)
    // send some json


evhttp_set_cb(_http, "/upload", upload_cb, NULL);
evhttp_set_cb(_http, "/status", status_cb, NULL);

当我上传一个大约 10 Mb 的文件并同时每 1 秒轮询一次状态 URL 时,状态 URL 将在大约 12 秒内没有响应,直到文件被正确处理并返回函数。

upload_cb 需要一段时间来处理数据,因此会阻止 status_cb 执行。 这是不可取的,因为此时应用程序没有响应。

我正在尝试不断地从应用程序中获取状态,并让 UI 及时了解正在发生的事情。 据我所知,一旦所有数据都被缓冲并准备好让回调处理它,就会调用upload_cb回调。这意味着它将在内存中缓冲整个 10 Mb。

我试图降低缓冲区的优先级,希望事件调度程序会中断并给其他事件一些时间,但我对 libevent 如何工作的想法似乎是不正确的。

我正在寻找替代解决方案,并觉得我可以创建一个单独的线程,我可以在其中处理上传 + 保存到文件,然后将控制权交给主线程。 但我更愿意在这里问是否有更优雅的解决方案,包括完全使用 libevent。

我可以让 libevent 中断处理上传并回复状态请求吗? 我可以强制 libevent 分块接收数据,以便它可以处理其他回调吗?


更新 1: 根据this question 我必须使用非阻塞 api 调用。尽管写入文件是非阻塞的,但似乎内核写入缓冲区已填满,因为我将 10+ Mb 写入文件。 我已将解析和写入移至允许 libevent 从函数返回的新线程,现在正在研究如何处理 post_buffer(多线程访问,使用后释放等)

更新 2: 将解析移至新线程不起作用,因为随后回调 upload_cb 返回并且 evhttp_request 及其缓冲区被清理,而线程仍要解析它. 我倾向于创建一个额外的 event_base 实例来处理this question链接中所述的无缝上传

【问题讨论】:

【参考方案1】:

我一直在网上阅读并找到了一个对我有用的解决方案,主要是受到以下代码的启发:Multi-Threaded HTTPServer using evhttp

struct eventbase *eventbase2;

void threadfunction()
    eventbase2 = event_base_new();
    struct evhttp* http2 = evhttp_new(event_base2);
    evhttp_set_cb(http2, "/upload", upload_cb, NULL);
    struct evhttp_bound_socket* handle = 
        evhttp_bind_socket_with_handle(http2, "0.0.0.0", 8080);
    event_base_dispatch(event_base2);  // <-------- it will wait here
    evhttp_del_accept_socket(http2, handle);
    evhttp_free(http2);
    event_base_free(event_base2);


// initialize a thread with the threadfunction

我创建了一个使用threadfunction 作为其线程函数的新线程(上面的代码中未显示)。该函数托管第二个eventbase,因此可以调用上传 url,并且可以根据需要花费尽可能多的时间。由于状态 url 由第一个 eventbase 处理,因此它将并行工作,并且上传 url 不会在它执行其操作时阻止它。

要停止正在运行的线程,您应该调用

event_base_loopbreak(event_base2);

然后可能在你的线程之后清理。

【讨论】:

我还添加了 libevhtp 库,它允许为上传的块注册回调。这样我就不必在内存中缓冲整个文件,并且可以在上传时将内容直接写入文件。

以上是关于发布多部分文件上传时的 libevent 块的主要内容,如果未能解决你的问题,请参考以下文章

如何以小块上传带有ajax的文件并检查失败,重新上传失败的部分。

截取部分字符串中的内容(可做文件上传时的文件重命名)

使用 Alamofire 上传多部分表单数据文件

Ajax上传文件怎样才能分块上传呢?

javaweb VUE+ElementUI 文件上传 后端部分

vue上传文件到后台