这里写自定义目录标题
Server-Sent Events
服务器向浏览器推送信息,除了 WebSocket,还有一种方法:Server-Sent Events(以下简称 SSE)。
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。
Mongoose
Mongoose is a networking library for C/C++. It implements event-driven, non-blocking APIs for TCP, UDP, HTTP, WebSocket, MQTT. It connects devices and brings them online. Since 2004, a number of open source and commercial products have utilized it. It even runs on the International Space Station! Mongoose makes embedded network programming fast, robust, and easy.
Mongoose works on Windows, Linux, Mac, and on a many embedded architectures such as STM32, NXP, TI, ESP32, and so on. It can run on top of the existing OS and TCP/IP stack like FreeRTOS and lwIP, as well as on a bare metal, utilising Mongoose’s built-in TCP/IP stack and network drivers.
代码实现
计时器事件处理程序获取一个指向事件管理器的指针作为其参数,我们需要它遍历所有连接,并在初始化部分进行配置:
// An highlighted block
static const char *s_listening_address = "http://0.0.0.0:8000";
static const char *s_root_dir = "web_root";
int main(void)
{
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, s_listening_address, cb, NULL);
mg_timer_add(&mgr, 500, MG_TIMER_REPEAT, timer_callback, &mgr);
// Start infinite event loop
MG_INFO(("Mongoose version : v%s", MG_VERSION));
MG_INFO(("Listening on : %s", s_listening_address));
MG_INFO(("Web root : [%s]", s_root_dir));
for (;;)
mg_mgr_poll(&mgr, 50);
mg_mgr_free(&mgr);
return 0;
}
当我们收到 URI 的 HTTP 请求时,我们标记该连接并提供一个标头,指示浏览器避免缓存该内容,并指示我们的响应内容类型将是 ,然后我们设置一个花哨的边界标记,用于分隔帧
#include "mongoose.h"
// HTTP request handler function. It implements the following endpoints:
// /api/video1 - hangs forever, returns MJPEG video stream
// all other URI - serves web_root/ directory
static void cb(struct mg_connection *c, int ev, void *ev_data)
{
if (ev == MG_EV_HTTP_MSG)
{
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
if (mg_match(hm->uri, mg_str("/api/video1"), NULL))
{
c->data[0] = 'V'; // Mark that connection as video live streamer
mg_printf(
c, "%s",
"HTTP/1.0 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Pragma: no-cache\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\n"
"Content-Type: multipart/x-mixed-replace; boundary=--foo\r\n\r\n");
}
else
if (mg_match(hm->uri, mg_str("/sse"), NULL))
{
c->data[0] = 'E'; // Mark that connection as event live streamer
mg_printf(
c, "%s",
"HTTP/1.0 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Transfer-Encoding: chunked\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-Type: text/event-stream; boundary=--foo\r\n\r\n");
}
else
{
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, ev_data, &opts);
}
}
}
然后,我们定期遍历所有标记的连接,并发送边界标记和新图像
static void broadcast_events(struct mg_mgr *mgr)
{
const char *files[] = {"images/1.jpg", "images/2.jpg", "images/3.jpg",
"images/4.jpg", "images/5.jpg", "images/6.jpg"};
size_t nfiles = sizeof(files) / sizeof(files[0]);
static size_t i;
const char *path = files[i++ % nfiles];
struct mg_str data = mg_file_read(&mg_fs_posix, path); // Read next file
struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next)
{
if (c->data[0] != 'E')
continue; // Skip non-stream connections
if (data.buf == NULL)
continue; // Skip on file read error
mg_printf(c,
"--foo\r\nContent-Type: image/jpeg\r\n"
"Content-Length: %lu\r\n\r\n",
data.len);
mg_printf(c, "%s", "data: 1231");
mg_send(c, data.buf, data.len);
mg_send(c, "\r\n", 2);
}
free((void *)data.buf);
}
static void timer_callback(void *arg)
{
broadcast_mjpeg_frame(arg);
broadcast_events(arg);
}