嵌入式web服务器之mongoose和goahead

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();
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值