mongoose httpserver浅析

文章目录

  • 前言
  • 一、结构体及其功能
  • 二、函数
      • MG_LOG
      • mg_http_listen
      • mg_mgr_poll
  • question
  • 参考链接


前言

mongoose是一款基于C/C++的网络库,可以实现TCP, UDP, HTTP, WebSocket, MQTT通讯。mongoose是的嵌入式网络程序更快、健壮,易于实现。

mongoose只有mongoose.c和mongoose.h两个文件,其它的例子基本是基于这两个文件加上对应的xxx.c文件。mongoose.ws/documentation/介绍了mongoose.h文件中的API。

它通过单向链表维护所有连接的client对象,其数据结构不是线程安全的。在httserver中,该程序是单线成的通过epoll模式处理连接,因此比较适合嵌入式等硬件资源有限的条件,在不修改源码的情况下并发性能受限。


一、结构体及其功能

struct mg_mgr
struct mg_mgr为时间管理结构体,保存一系列的链接
conns指向单向链表的头部,每有新的链接都LIST_ADD_HEAD

struct mg_mgr {struct mg_connection *conns;  // 每个连接都是一个struct mg_connection,指向连接组成的单向链表的头部struct mg_dns dns4;           // DNS for IPv4struct mg_dns dns6;           // DNS for IPv6int dnstimeout;               // DNS resolve timeout in millisecondsbool use_dns6;                // Use DNS6 server by default, see #1532unsigned long nextid;         // Next connection ID,该数字逐渐增大,不会减小 ?unsigned long timerid;        // Next timer IDvoid *userdata;               // Arbitrary user data pointervoid *tls_ctx;                // TLS context shared by all TLS sessionsuint16_t mqtt_id;             // MQTT IDs for pub/subvoid *active_dns_requests;    // DNS requests in progressstruct mg_timer *timers;      // Active timersint epoll_fd;                 // Used when MG_EPOLL_ENABLE=1void *priv;                   // Used by the MIP stacksize_t extraconnsize;         // Used by the MIP stackMG_SOCKET_TYPE pipe;          // Socketpair end for mg_wakeup()
#if MG_ENABLE_FREERTOS_TCPSocketSet_t ss;  // NOTE(lsm): referenced from socket struct
#endif
};

struct mg_connection
每次调用accept函数获得一个有效的fd时都新建一个该对象并将其加入单向链表中,保存了连接的client的相关信息

struct mg_connection {struct mg_connection *next;  // 指向下一个clientstruct mg_mgr *mgr;          // Our containerstruct mg_addr loc;          // host地址信息struct mg_addr rem;          // client地址信息void *fd;                    // Connected socket, or LWIP dataunsigned long id;            // Auto-incrementing unique connection ID,给client的唯一id,但不一定连续不知道有什么含义struct mg_iobuf recv;        // Incoming datastruct mg_iobuf send;        // Outgoing datastruct mg_iobuf prof;        // Profile data enabled by MG_ENABLE_PROFILEstruct mg_iobuf rtls;        // TLS only. Incoming encrypted datamg_event_handler_t fn;       // User-specified event handler function,在main中,cbvoid *fn_data;               // User-specified function parametermg_event_handler_t pfn;      // Protocol-specific handler function,处理协议的函数,如http_cbvoid *pfn_data;              // Protocol-specific function parameterchar data[MG_DATA_SIZE];     // Arbitrary connection datavoid *tls;                   // TLS specific data// 位域,下面这么多总共占4个字节unsigned is_listening : 1;   // Listening connection, 是否为监听fdunsigned is_client : 1;      // Outbound (client) connection  mongoose作为client程序时会用到unsigned is_accepted : 1;    // Accepted (server) connection  接受来自client的链接时设为1(在accept_conn处)// 在http server中并 监听描述符以及client描述符都是非阻塞的unsigned is_resolving : 1;   // Non-blocking DNS resolution is in progressunsigned is_arplooking : 1;  // Non-blocking ARP resolution is in progressunsigned is_connecting : 1;  // Non-blocking connect is in progressunsigned is_tls : 1;         // TLS-enabled connectionunsigned is_tls_hs : 1;      // TLS handshake is in progressunsigned is_udp : 1;         // UDP connectionunsigned is_websocket : 1;   // WebSocket connectionunsigned is_mqtt5 : 1;       // For MQTT connection, v5 indicatorunsigned is_hexdumping : 1;  // Hexdump in/out trafficunsigned is_draining : 1;    // Send remaining data, then close and freeunsigned is_closing : 1;     // Close and free the connection immediatelyunsigned is_full : 1;        // Stop reads, until clearedunsigned is_resp : 1;        // Response is still being generated,生在生成c->sendunsigned is_readable : 1;    // Connection is ready to readunsigned is_writable : 1;    // Connection is ready to write
};

struct mg_http_message
存储解析后的http信息

struct mg_str {const char *ptr;  // Pointer to string datasize_t len;       // String len
};struct mg_http_header {struct mg_str name;   // Header namestruct mg_str value;  // Header value
};struct mg_http_message {struct mg_str method, uri, query, proto;             // Request/response linestruct mg_http_header headers[MG_MAX_HTTP_HEADERS];  // Headers  MG_MAX_HTTP_HEADERS=30struct mg_str body;                                  // Bodystruct mg_str head;                                  // Request + headersstruct mg_str message;  // Request + headers + body
};

在这里插入图片描述

二、函数

MG_LOG

默认log级别为MG_LL_INFO=2

#define MG_ERROR(args) MG_LOG(MG_LL_ERROR, args)
#define MG_INFO(args) MG_LOG(MG_LL_INFO, args)
#define MG_DEBUG(args) MG_LOG(MG_LL_DEBUG, args)
#define MG_VERBOSE(args) MG_LOG(MG_LL_VERBOSE, args)#define MG_LOG(level, args)                                 \do {                                                      \if ((level) <= mg_log_level) {                          \mg_log_prefix((level), __FILE__, __LINE__, __func__); \mg_log args;                                          \}                                                       \} while (0)
// log的前缀
void mg_log_prefix(int level, const char *file, int line, const char *fname) {const char *p = strrchr(file, '/');char buf[41];size_t n;if (p == NULL) p = strrchr(file, '\\');n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level,p == NULL ? file : p + 1, line, fname);if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;while (n < sizeof(buf)) buf[n++] = ' ';logs(buf, n - 1);
}
// 打印内容
void mg_log(const char *fmt, ...) {va_list ap;va_start(ap, fmt);mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);va_end(ap);logs("\r\n", 2);
}

mg_http_listen

struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url,mg_event_handler_t fn, void *fn_data)

该函数精简后类似于:

if ( (fd = socket(af, type, proto)) == -1 ) {MG_ERROR(("socket: %d", MG_SOCK_ERR(-1)));
} else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on))) != 0) {MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc)));
} else if ((rc = bind(fd, &usa.sa, slen)) != 0) {MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
} else if ( (rc = listen(fd, 128)) != 0 ) {MG_ERROR(("listen: %d", MG_SOCK_ERR(rc)));
} else {// 这里考虑到了是否有ipv6所以掉了个函数处理setlocaddr(fd, &c->loc);  // 将host地址写入监听描述符对应的struct mg_connection中mg_set_non_blocking_mode(fd);  // 设置描述符的O_NONBLOCK以及FD_CLOEXECc->fd = S2PTR(fd);MG_EPOLL_ADD(c);  // 加入epollsuccess = true;
}
设置监听描述符对应的结构体并将其加入mgr的conns链表

MG_EPOLL_X宏
水平触发模式

#define MG_EPOLL_ADD(c)                                                    \do {                                                                     \struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}};          \epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_ADD, (int) (size_t) c->fd, &ev); \} while (0)
#define MG_EPOLL_MOD(c, wr)                                                \do {                                                                     \struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}};          \if (wr) ev.events |= EPOLLOUT;                                         \epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_MOD, (int) (size_t) c->fd, &ev); \} while (0)

mg_mgr_poll

man函数最终会进入while (s_signo == 0) mg_mgr_poll(&mgr, 1000); 死循环中

在1000并发量时测试点:

  1. max有多大
  2. n = epoll_wait有多大
  3. 链表的大小是多少,其中有效的有多少个,无效的有多少个
  4. 最大文件描述符的值是多少
void mg_mgr_poll(struct mg_mgr *mgr, int ms) {struct mg_connection *c, *tmp;
// 获取epoll通知(epoll_wait),设置对应mg_connection的标志位mg_iotest(mgr, ms);// 遍历单向链表for (c = mgr->conns; c != NULL; c = tmp) {tmp = c->next;if (c->is_resolving || c->is_closing) {// Do nothing} else if (c->is_listening && c->is_udp == 0) {if (c->is_readable) accept_conn(mgr, c);} else if (c->is_connecting) {  // http server中这里几乎一直处于0if (c->is_readable || c->is_writable) connect_conn(c);} else {if (c->is_readable) read_conn(c);if (c->is_writable) write_conn(c);}if (c->is_draining && c->send.len == 0) c->is_closing = 1;if (c->is_closing) close_conn(c);}
}static void mg_iotest(struct mg_mgr *mgr, int ms) {size_t max = 1;for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {c->is_readable = c->is_writable = 0;if (c->rtls.len > 0) ms = 1, c->is_readable = 1;if (can_write(c)) MG_EPOLL_MOD(c, 1);  // 只要c->send.len > 0就触发EPOLLOUTif (c->is_closing) ms = 1;max++;}struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0]));int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);for (int i = 0; i < n; i++) {struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr;if (evs[i].events & EPOLLERR) {mg_error(c, "socket error");  // 当c->send.len的大小和http报文的Content-Length大小不对应时会导致EPOLLERR,原因未知} else if (c->is_readable == 0) {bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);bool wr = evs[i].events & EPOLLOUT;c->is_readable = can_read(c) && rd ? 1U : 0;c->is_writable = can_write(c) && wr ? 1U : 0;if (c->rtls.len > 0) c->is_readable = 1;}}
}

在一个mg_mgr_poll循环中受限通过mg_iotest将对应事件的标志位进行设置is_readable,is_writable,再通过循环即标志位处理对应事件

accept_conn函数通过accept获取client fd然后对其结构体和fd进行设置

static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {struct mg_connection *c = NULL;union usa usa; socklen_t sa_len = sizeof(usa);int fd = accept(FD(lsn), &usa->sa, &sa_len);if (fd < 0) {MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERR(-1)));} else if ((c = mg_alloc_conn(mgr)) == NULL) {MG_ERROR(("%lu OOM", lsn->id));close(fd);} else {tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));  // 将remote地址写入c->remLIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);c->fd = S2PTR(fd);MG_EPOLL_ADD(c);mg_set_non_blocking_mode(FD(c));  // 设置描述符的O_NONBLOCK以及FD_CLOEXECsetsockopts(c);                   // setsockoptc->is_accepted = 1;c->is_hexdumping = lsn->is_hexdumping;c->loc = lsn->loc;c->pfn = lsn->pfn;c->pfn_data = lsn->pfn_data;c->fn = lsn->fn;c->fn_data = lsn->fn_data;MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd, mg_print_ip_port,&c->rem, mg_print_ip_port, &c->loc));// http server 下无事发生mg_call(c, MG_EV_OPEN, NULL);mg_call(c, MG_EV_ACCEPT, NULL);}
}

read_conn函数非阻塞读到c->recv.buf中,然后交由iolog处理。iolog会判断n的返回值。if EINPROGRESS || EWOULDBLOCK则什么也不做;elif<=0则设is_closing = 1,随后会TODO:;elif n>0 则调用mg_call(c, MG_EV_READ, &n),它通过函数指针调用http_cb函数解析http协议。
static void http_cb(struct mg_connection *c, int ev=待处理事件(该函数只处理MG_EV_READ,MG_EV_CLOSE), void *ev_data=未使用)函数会解析c->recv.buf中的http协议(解析字符),之后f (c->is_accepted) c->is_resp = 1;调用mg_call(c, MG_EV_HTTP_MSG, &hm);将http解析结果交由cb函数进行处理该函数会写c->send.buf, return; 然后清除c->recv,http_cb return。
iolog(c, buf, n, true);返回后read_conn结束。

n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);static void read_conn(struct mg_connection *c) {if (ioalloc(c, &c->recv)) {char *buf = (char *) &c->recv.buf[c->recv.len];size_t len = c->recv.size - c->recv.len;long n = -1;n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);  // 文件描述符是非阻塞的,非阻塞接收// n 经过处理后可为 -1 -2 -3iolog(c, buf, n, true);}
}

write_conn函数通过send函数将c->send.buf发送,然后调用iolog
对n处理。然后清理c->send,然后if (c->send.len == 0)MG_EPOLL_MOD(c, 0);再掉mg_call(c, MG_EV_WRITE, &n);iolog return后write_conn也结束

static void write_conn(struct mg_connection *c) {long n = send(FD(c), c->send.buf, c->send.len, MSG_NONBLOCKING);// 文件描述符是非阻塞的,非阻塞接收// n 经过处理后可为 -1 -2 -3iolog(c, buf, n, false);
}

question

  1. mg_http_reply函数,当继续调用mg_printf(c, fmt, …)而不修改Content-Length时 浏览器不能接收全部的数据?
  2. is_closing何时被设置为1的?如果client发一次,server发一次不会触发is_closing=1,莫非是等待conn_fd断开的时候触发EPOLLHUP然后间接关闭?若是如此如何触发的问题1?

参考链接

https://mongoose.ws/documentation/

https://github.com/cesanta/mongoose

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/265326.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 0938.二叉搜索树的范围和:深度优先搜索(可中序遍历)

【LetMeFly】938.二叉搜索树的范围和&#xff1a;深度优先搜索&#xff08;可中序遍历&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/range-sum-of-bst/ 给定二叉搜索树的根结点 root&#xff0c;返回值位于范围 [low, high] 之间的所有结点的值的和。…

【踩坑】PyTorch中指定GPU不生效和GPU编号不一致问题

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 指定GPU不生效问题 解释&#xff1a;就是使用os.environ["CUDA_VISIBLE_DEVICES"] "1"后&#xff0c;后面使用起来仍然是cuda0. 解决&#xff1a;在最开头就使用 import os os.environ[&…

测试开发(6)软件测试教程——自动化测试selenium(自动化测试介绍、如何实施、Selenium介绍 、Selenium相关的API)

接上次博客&#xff1a;测试开发&#xff08;5&#xff09;测试分类标准 &#xff1a;按测试对像划分、按是否查看代码划分、按开发阶段划分、按测试实施组织、按是否运行划分、按是否手工划分、按测试地域划分-CSDN博客 目录​​​​​​​ 什么是自动化测试 自动化测试介绍…

【React架构 - Scheduler中的MessageChannel】

前序 我们都知道JS代码是在浏览器5个进程(下面有介绍)中渲染进程中的Js引擎线程执行的&#xff0c;其他还有GUI渲染线程、定时器线程等&#xff0c;而页面的布局和绘制是在GUI线程中完成的&#xff0c;这些线程之间是互斥的&#xff0c;所以在执行Js的同时会阻塞页面的渲染绘制…

MCU最小系统电路设计(以STM32F103C8T6为例)

目录 一、何为最小系统&#xff1f; 二、最小系统电路设计 1.电源 &#xff08;1&#xff09;各种名词解释 &#xff08;2&#xff09;为什么会有VDD_1 _2 _3区分&#xff1f; &#xff08;3&#xff09;Mirco USB &#xff08;4&#xff09;5v->3.3v滤波电路 &#…

VsCode的leetcode插件无法登录

前提 想使用VsCode的leetcode插件进行刷题&#xff0c;然后按照网上的教程进行安装下载&#xff0c;但是到了登录这一步&#xff0c;死活也登录不了&#xff0c;然后查看log一直报的错误是invalid password。 解决方法 首先确定在插件中设置的站点是Leetcode中国&#xff0c…

面试笔记系列四之SpringBoot+SpringCloud基础知识点整理及常见面试题

什么是 Spring Boot&#xff1f; Spring Boot 是 Spring 开源组织下的子项目&#xff0c;是 Spring 组件一站式解决方案&#xff0c;主要是简化了使用 Spring 的难度&#xff0c;简省了繁重的配置&#xff0c;提供了各种启动器&#xff0c;开发者能快速上手。 Spring Boot 有哪…

nodejs 实现pdf与图片互转

PDF转图片 效果图 代码 const path require(path); const pdf require(pdf-poppler); const fs require(fs); // PDF文件路径 const pdfFilePath ./path/test.pdf; // 转换选项 const opts { format: png, // 输出图片格式&#xff0c;可以是 jpeg, png, ppm…

Springboot+vue的考务报名平台(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的考务报名平台&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的考务报名平台&#xff0c;采用M&#xff08;model&#xff0…

Redis7

摘录 https://github.com/Romantic-Lei/Learning-in-practice/blob/master/Redis/ 官网地址: 英文&#xff1a;Redis 中文&#xff1a;CRUG网站 redis中文文档 安装包&#xff1a;https://redis.io/download/&#xff0c;选择redis7.0版本即可 Redis在线测试地址(不用下载也…

jmeter(四)HTTP请求

启动jmeter&#xff0c;建立一个测试计划 这里再次说说怎么安装和启动jmeter吧&#xff0c;昨天下午又被人问到怎样安装和使用&#xff0c;我也是醉了&#xff1b;在我看来&#xff0c;百度能解决百分之八十的问题&#xff0c;特别是基础的问题。。。 安装&#xff1a;去官网…

黑马程序员——接口测试——day03——Postman断言、关联、参数化

目录&#xff1a; Potman断言 Postman断言简介Postman常用断言 断言响应状态码断言包含某字符串断言JSON数据Postman断言工作原理Postman关联 简介实现步骤核心代码创建环境案例1案例2Postman参数化 简介数据文件简介编写数据文件 CSV文件JSON文件导入数据文件到postman读取数…

springboot-基础-添加model和controller的简单例子+常用注解含义

备份笔记。所有代码都是2019年测试通过的&#xff0c;如有问题请自行搜索解决&#xff01; 上一篇&#xff1a;springboot-基础-eclipse配置helloword示例 目录 添加model和controller的例子注解开发使用RestController 大坑 Model ModelMap和ModelAndView的区别 添加model和c…

Python算法100例-2.6 分糖果

完整源代码项目地址&#xff0c;关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.运行结果 1&#xff0e;问题描述 10个小孩围成一圈分糖果&#xff0c;老师分给第1个小孩10块&#xff0c;第2个小孩2块&#xff0c;第3个小孩8块&…

vue 中实现音视频播放进度条(满足常见开发需求)

由于开发需要&#xff0c;作者封装了一个音视频播放进度条的插件&#xff0c;支持 vue2 及 vue3 &#xff0c;有需要的朋友后台私信作者获取插件哦&#xff0c;下面是对该款插件的介绍。 插件默认样式&#x1f447;&#xff08;插件提供了多个配置选项&#xff0c;可根据自身需…

深度访谈 | 模块化建筑让设计和建造更简单!

从汽车行业转行建筑行业转行建筑行业&#xff0c;将汽车工业理念和建筑营造结合。大咖面对面&#xff0c;优积科技CEO刘其东接受自媒体视频号——Elaine深度专访。解读像造汽车一样造房子。 汽车行业和建筑行业有何渊源&#xff1f;到底是什么样的机缘巧合使一帮造汽车的团队要…

PDF文件转换为图片

现在确实有很多线上的工具可以把pdf文件转为图片&#xff0c;比如smallpdf等等&#xff0c;都很好用。但我们有时会碰到一些敏感数据&#xff0c;或者要批量去转&#xff0c;那么需要自己写脚本来实现&#xff0c;以下脚本可以提供这个功能~ def pdf2img(pdf_dir, result_path…

低代码与大语言模型的探索实践

低代码系列文章&#xff1a; 可视化拖拽组件库一些技术要点原理分析可视化拖拽组件库一些技术要点原理分析&#xff08;二&#xff09;可视化拖拽组件库一些技术要点原理分析&#xff08;三&#xff09;可视化拖拽组件库一些技术要点原理分析&#xff08;四&#xff09;低代码…

MQTT协议解析:揭秘固定报头、可变报头与有效载荷的奥秘

MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;是一种轻量级的通讯协议&#xff0c;常用于远程传感器和控制设备的通讯。MQTT协议基于发布/订阅模式&#xff0c;为大量计算能力有限且工作在低带宽、不可靠网络环境中的设备…

Java设计模式 | 简介

设计模式的重要性&#xff1a; 软件工程中&#xff0c;设计模式&#xff08;design pattern&#xff09;是对软件设计中普遍存在&#xff08;反复出现&#xff09;的各种问题&#xff0c;所提出的解决方案。 这个术语由埃里希 伽玛&#xff08;Erich Gamma&#xff09;等人在1…