mongoose官网:https://cesanta.com/docs/overview/intro.html
一、mongoose简介
Mongoose是一个web服务器,通过提供一个web接口给它,它可以嵌入到现有的应用程序中去。Mongooseweb服务器的执行是自满足的,它不依赖于任何其他服务。
如果你将它复制到任何目录并执行,它将将启动web服务并将当前目录作为主目录、端口号是8080。当然这些配置选项都可以通过配置文件mongoose.conf设置。
Mongoose它完全开源和自由使用。整个代码也只有一个mongoose.c和mongoose.h两个文件。在HTTP方面,Mongoose支持大量功能。它包括一个HTTP客户端和HTTP服务器。
发送和接收文件(任何大小),CGI,Cookie身份验证,静态客户端和服务器等等。
我们确保大多数功能都非常易于使用。只需调用mg_set_protocol_http_websocket进行连接,即可建立HTTP连接。
Mongoose是一个用C语言编写的网络库。它为客户端和服务器模式实现了用于TCP,UDP,HTTP,WebSocket,CoAP,MQTT的事件驱动的非阻塞API。功能包括:
跨平台:适用于Linux / UNIX,MacOS,QNX,eCos,Windows,Android,iPhone,FreeRTOS
对PicoTCP嵌入式TCP / IP堆栈, LWIP嵌入式TCP / IP堆栈的本机支持
适用于各种嵌入式板卡:TI CC3200,TI MSP430,STM32,ESP8266;在所有基于Linux的板上,例如Raspberry PI,BeagleBone等
具有简单的基于事件的API的单线程,异步,非阻塞内核
内置协议:
普通TCP,普通UDP,SSL / TLS(单向或双向),客户端和服务器
HTTP客户端和服务器
WebSocket客户端和服务器
MQTT客户端和服务器
CoAP客户端和服务器
DNS客户端和服务器
异步DNS解析器
微小的静态和运行时占用空间
源代码均符合ISO C和ISO C ++
易于集成:只需将 mongoose.c和 mongoose.h 文件复制到构建树中
二、设计理念
1、mangoose具有三个基本数据结构:
struct mg_mgr 是拥有所有活动连接的事件管理器
struct mg_connection 描述连接
struct mbuf 描述数据缓冲区(接收或发送的数据)
2、使用mangoose的应用程序应遵循事件驱动应用程序的标准模式:
1、声明和初始化事件管理器
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL);
2、创建连接。例如,服务器应用程序应创建监听连接:
struct mg_connection *c = mg_bind(&mgr, "80", ev_handler_function);
mg_set_protocol_http_websocket(c);
3、通过调用循环来创建事件mg_mgr_poll()循环:
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_poll()遍历所有套接字,接受新连接,发送和接收数据,关闭连接并为相应事件调用事件处理函数
3、内存缓冲区
每个连接有一个发送和接收缓冲区,struct mg_connection::send_mbuf 并struct mg_connection::recv_mbuf分别。数据到达后,Mongoose将收到的数据附加到recv_mbuf并触发一个MG_EV_RECV 事件。
用户可以通过调用输出功能之一(例如mg_send()或)将数据发送回去 mg_printf()。输出函数将数据附加到send_mbuf。当Mongoose成功将数据写入套接字时,
它将丢弃数据 struct mg_connection::send_mbuf并发送MG_EV_SEND事件。关闭连接后,将MG_EV_CLOSE发送事件。
4、事件处理函数
每个连接都有一个与之关联的事件处理函数。该功能必须由用户实现。事件处理程序是Mongoose应用程序的关键元素,因为它定义了应用程序的行为。这是事件处理程序函数的样子:
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch (ev) {
/* Event handler code that defines behavior of the connection */
...
}
}
struct mg_connection *nc:已收到事件的连接。
int ev:事件编号,在中定义mongoose.h。例如,当数据到达入站连接时,ev将为MG_EV_RECV。
void *ev_data:此指针指向事件特定的数据,并且对于不同的事件具有不同的含义。例如,对于一个MG_EV_RECV事件,它 ev_data是一个int *指针,
指向从远程对等方接收并保存到接收IO缓冲区中的字节数。ev_data针对每个事件描述了的确切含义 。协议特定的事件通常 ev_data指向保存协议特定信息的结构。
5、事件
MG_EV_ACCEPT:当侦听连接接受新的服务器连接时发送。
MG_EV_CONNECT:当mg_connect() 连接成功时发送。
MG_EV_RECV:接收到新数据并将其附加到的末尾recv_mbuf。通常,事件处理程序应该检入接收到的数据nc->recv_mbuf,通过调用来丢弃已处理的数据 mbuf_remove(),
nc->flags在必要时设置连接标志,并通过输出功能(如)将数据写入远程对等体 mg_send()。
MG_EV_SEND:猫鼬已将数据写入远程对等端,并丢弃了来自mg_connection::send_mbuf。
注意:mangoose输出函数仅将数据追加到 mg_connection::send_mbuf。他们不执行任何套接字写操作。实际的IO由完成mg_mgr_poll()。一个MG_EV_SEND事件是关于IO已经完成只是一个通知。
MG_EV_POLL:每次调用时发送到所有连接mg_mgr_poll()。此事件可用于执行任何内务处理,例如检查某个超时是否已到期并关闭连接或发送心跳消息等。
MG_EV_TIMER:发送到连接(如果mg_set_timer()已调用)。
MG_EV_HTTP_REQUEST:HTTP请求已到达。解析的请求struct http_message通过处理程序的void *ev_data指针传递 。
MG_EV_HTTP_REPLY:HTTP回复已到达。解析的回复将通过struct http_message处理程序的void *ev_data 指针传递。
6、连接标志
以下是由事件处理程序设置的连接标志的列表:
MG_F_SEND_AND_CLOSE告诉Mongoose,所有数据都已附加到send_mbuf。Mongoose将其发送到套接字后,连接将立即关闭。
MG_F_BUFFER_BUT_DONT_SEND告诉Mongoose将数据附加到,send_mbuf 但仍保留发送,因为稍后将修改数据,然后通过清除MG_F_BUFFER_BUT_DONT_SEND标志来发送数据。
MG_F_CLOSE_IMMEDIATELY 告诉Mongoose通常在发生错误后立即关闭连接。
MG_F_USER_1,MG_F_USER_2,MG_F_USER_3,MG_F_USER_4可以由开发者用来存储特定于应用的状态。
下面的标志是由Mongoose设置的:
MG_F_SSL_HANDSHAKE_DONE 仅SSL,SSL握手完成后设置。
MG_F_CONNECTINGmg_connect()通话后连接处于连接状态 但连接尚未完成时设置。
MG_F_LISTENING 为所有监听连接设置。
MG_F_UDP 如果连接是UDP,则设置。
MG_F_IS_WEBSOCKET 如果连接是WebSocket连接,则设置。
MG_F_WEBSOCKET_NO_DEFRAG 如果用户要关闭自动WebSocket框架碎片整理,则应由用户设置。
7、构建选项
启用标志
MG_ENABLE_SSL启用SSL / TLS支持(OpenSSL API)
MG_ENABLE_IPV6 启用IPv6支持
MG_ENABLE_MQTT启用MQTT客户端(默认情况下处于启用状态,设置为0以禁用)
MG_ENABLE_MQTT_BROKER启用MQTT经纪人
MG_ENABLE_DNS_SERVER 启用DNS服务器
MG_ENABLE_COAP 启用CoAP协议
MG_ENABLE_HTTP 启用HTTP协议支持(默认情况下,设置为0以禁用)
MG_ENABLE_HTTP_CGI启用CGI支持
MG_ENABLE_HTTP_SSI启用服务器端包含支持
MG_ENABLE_HTTP_SSI_EXEC启用S??SI exec运算符
MG_ENABLE_HTTP_WEBDAV 启用对HTTP的WebDAV扩展
MG_ENABLE_HTTP_WEBSOCKET 启用对HTTP的WebSocket扩展(默认情况下为= 0禁用)
MG_ENABLE_BROADCAST启用mg_broadcast()API
MG_ENABLE_GETADDRINFO能够getaddrinfo()在mg_resolve2()
MG_ENABLE_THREADS启用mg_start_thread()API
禁用标志
MG_DISABLE_HTTP_DIGEST_AUTH 禁用HTTP摘要(MD5)授权支持
CS_DISABLE_SHA1 禁用SHA1支持(由WebSocket使用)
CS_DISABLE_MD5 禁用MD5支持(由HTTP身份验证使用)
MG_DISABLE_HTTP_KEEP_ALIVE 有助于嵌入式系统节省资源
要在编译期间设置预处理器标志,请使用-D <PREPROCESSOR_FLAG> 编译器选项。例如,要禁用MQTT和CoAP,请my_app.c像这样编译应用程序(假定为UNIX系统):
$ cc my_app.c mongoose.c -D MG_DISABLE_MQTT -D MG_DISABLE_COAP
8、http服务器模型
1、通过调用mg_bind()或创建监听连接mg_bind_opt()
2、呼叫mg_set_protocol_http_websocket()该监听连接。它附加了一个内置的HTTP事件处理程序,该处理程序分析传入的数据并触发特定于HTTP的事件。
例如,当HTTP请求被完全缓冲时,内置的HTTP处理程序将解析该请求,并使用MG_EV_HTTP_REQUEST事件和已解析的HTTP请求作为事件数据来调用用户定义的事件处理程序。
3、创建事件处理函数。请注意,事件处理程序会接收所有事件,例如低级TCP事件MG_EV_RECV和高级HTTP事件,例如MG_EV_HTTP_REQUEST。通常,事件处理函数只能处理MG_EV_HTTP_REQUEST事件。
4、这是最简单的HTTP服务器的示例。为了清楚起见,省略了错误检查:
#include "mongoose.h"
static const char *s_http_port = "8000";
static void ev_handler(struct mg_connection *c, int ev, void *p) {
if (ev == MG_EV_HTTP_REQUEST) {
struct http_message *hm = (struct http_message *) p;
// We have received an HTTP request. Parsed request is contained in `hm`.
// Send HTTP reply to the client which shows full original request.
mg_send_head(c, 200, hm->message.len, "Content-Type: text/plain");
mg_printf(c, "%.*s", (int)hm->message.len, hm->message.p);
}
}
int main(void) {
struct mg_mgr mgr;
struct mg_connection *c;
mg_mgr_init(&mgr, NULL);
c = mg_bind(&mgr, s_http_port, ev_handler);
mg_set_protocol_http_websocket(c);
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
9、HTTP客户端模型
1、通过调用创建出站连接 mg_connect_http()
2、创建一个处理MG_EV_HTTP_REPLY事件的事件处理函数
3、这是最简单的HTTP客户端的示例。为了清楚起见,省略了错误检查:
#include "mongoose.h"
static const char *url = "http://www.google.com";
static int exit_flag = 0;
static void ev_handler(struct mg_connection *c, int ev, void *p) {
if (ev == MG_EV_HTTP_REPLY) {
struct http_message *hm = (struct http_message *)p;
c->flags |= MG_F_CLOSE_IMMEDIATELY;
fwrite(hm->message.p, 1, (int)hm->message.len, stdout);
putchar('\n');
exit_flag = 1;
} else if (ev == MG_EV_CLOSE) {
exit_flag = 1;
};
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL);
mg_connect_http(&mgr, ev_handler, url, NULL, NULL);
while (exit_flag == 0) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
10、提供文件
为了创建一个从当前目录提供静态文件的Web服务器,请实现事件处理程序功能,如下所示
static void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_REQUEST) {
struct mg_serve_http_opts opts;
memset(&opts, 0, sizeof(opts); // Reset all options to defaults
opts.document_root = "."; // Serve files from the current directory
mg_serve_http(c, (struct http_message *) ev_data, s_http_server_opts);
}
}
11、有时,不需要实现完整的静态Web服务器,例如,如果在一台RESTful服务器上工作。如果某些端点必须返回静态文件的内容,mg_http_serve_file()则可以使用一个更简单的函数:
static void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
switch (ev) {
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
mg_http_serve_file(c, hm, "file.txt",
mg_mk_str("text/plain"), mg_mk_str(""));
break;
}
...
}
}
12、文件上传
为了处理文件上传,请使用以下HTML代码段:
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
上载的文件将/upload通过POST请求发送到端点。HTTP正文将包含包含文件内容的多部分编码缓冲区。
要保存上传的文件,请使用以下代码段:
struct mg_str cb(struct mg_connection *c, struct mg_str file_name) {
// Return the same filename. Do not actually do this except in test!
// fname is user-controlled and needs to be sanitized.
return file_name;
}
void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
switch (ev) {
...
case MG_EV_HTTP_PART_BEGIN:
case MG_EV_HTTP_PART_DATA:
case MG_EV_HTTP_PART_END:
mg_file_upload_handler(c, ev, ev_data, cb);
break;
}
}
13、启用SSL
要在服务器端启用SSL,请按照以下步骤操作:
0、在mongoose.c的顶部定义#define MONGOOSE_USE_SSL
1、获取SSL证书文件和私钥文件
2、声明struct mg_bind_opts,初始化ssl_cert并ssl_key
3、使用mg_bind_opt()创建监听套接字
int main(void) {
struct mg_mgr mgr;
struct mg_connection *c;
struct mg_bind_opts bind_opts;
mg_mgr_init(&mgr, NULL);
memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.ssl_cert = "server.pem";
bind_opts.ssl_key = "server.key";
// Use bind_opts to specify SSL certificate & key file
c = mg_bind_opt(&mgr, "443", ev_handler, bind_opts);
mg_set_protocol_http_websocket(c);
...
}
服务器证书的生成
a)生成服务器私钥
openssl genrsa -des3 -out server.key 1024
输入加密密码,用 128 位 rsa 算法生成密钥,得到 server.key 文件。
b)生成服务器证书请求( CSR )
openssl req -new -key server.key -out server.csr
CSR( Certificate Signing Request)是一个证书签名请求,在申请证书之前,首先要在服务器上生成 CSR ,并将其提交给 CA 认证中心,
CA 才能签发 SSL 服务器证书。也可以认为, CSR 就是一个在服务器上生成的证书。
在生成这个文件的过程中,有一点需要特别注意,Common Name 填入主机名(或者服务器IP)。
c)自己生成服务器证书
如果不使用 CA 证书签名的话,用如下方式生成:
openssl req -x509 -days 1024 -key server.key -in server.csr > server.crt
用服务器密钥和证书请求生成证书 server.crt , -days 参数指明证书有效期,单位为天。商业上来说,服务器证书是由通过第三方机构颁发的,该证书由第三方认证机构颁发的。
如果使用 CA 证书签名,用 openssl 提供的工具 CA.sh 生成服务器证书:
mv server.csr newreq.pem
./CA.sh -sign
mv newcert.pem server.crt
签名证书后,可通过如下命令可查看服务器证书的内容:
openssl x509 -noout -text -in server.crt
可通过如下命令验证服务器证书:
openssl verify -CAfile ca.crt server.crt
客户证书的生成
客户证书是可选的。如果有客户证书,就是双向认证 HTTPS ,否则就是单向认证 HTTPS 。
a) 生成客户私钥
openssl genrsa -des3 -out client.key 1024
b) 生成客户证书签名请求
openssl req -new -key client.key -out client.csr
c) 生成客户证书(使用 CA 证书签名)
openssl ca -in client.csr -out client.crt
d) 证书转换成浏览器认识的格式
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.pfx
学习GoAhead直通车https://www.embedthis.com/goahead/doc/
一、GoAhead简介
GoAhead是世界上最受欢迎的嵌入式Web服务器。它简单,小巧,是高效托管嵌入式Web应用程序的理想选择。
GoAhead经过优化,可通过事件驱动的单线程内核托管动态嵌入式Web应用程序。它非常紧凑(115K代码),对于减少Web应用程序的每个请求的内存开销特别有效。
二、GoAhead 常用操作实现
GoActions 过程(旧版本使用的是GoForms,使用方式基本相同),绑定C函数为具体的URL链接,主要用于用于响应用户输入,更新设置或执行特定动作。
一般过程:
1.通过 websDefineAction 函数注册GoAction 函数,即绑定该 C 函数到 /action/test
2.在 route.txt 中添加对应action路径
route uri=/action handler=action
3.在html 文件中的表单中触发 http POST操作指定URL /action/test
三、mongoose 和 goahead服务端创建基本流程
1、定义数据类型
mongoose:static struct mg_serve_http_opts g_http_server_opts;
static struct mg_mgr g_mgr;
static struct mg_connection *g_nc;
goahead:static int finished = 0;
static void initPlatform(void);
static void sigHandler(int signo);
2、初始化、创建连接
mongoose: mg_mgr_init(&g_mgr, NULL);
g_nc = mg_bind(&g_mgr, port, ev_handler);
mg_set_protocol_http_websocket(g_nc);
goahead:initPlatform();
websOpen();
websLoad();
websListen();
3、注册动作:
mongoose:mg_register_http_endpoint(g_nc,endpoint,fn);
goahead: websAspDefine(endpoint,fn);
websDefineAction(endpoint,fn);
websUrlHandlerDefine(endpoint, NULL, 0, fn, 0);
4、循环调用事件:
mongoose: while (1)
mg_mgr_poll(&g_mgr, 1000);
goahead: while (finished == 0) {
if (socketSelect(-1, delay)) {
socketProcess();
}
5、关闭:
mongoose:
mg_mgr_free(&g_mgr);
goahead:
websClose();