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://xiahunao.cn/news/2807873.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL学习笔记4: MySQL表的增删查改 (基础)

目录 前言1. 新增 insert补充: 插入时间日期 (datetime 类型的数据) 2. 查询 select2.1. 全列查询 select * from 表名;2.2. 指定列查询 select 列名,列名... from 表名;2.3. 表达式查询 select 表达式 from 表名;2.4. 给查询结果指定别名 select 表达式 as 别名 from 表名;2.5…

如何在Python中创建动态图形?

动态图形是使可视化更具吸引力和用户吸引力的好方法。它帮助我们以有意义的方式展示数据可视化。Python帮助我们使用现有强大的Python库创建动态图形可视化。Matplotlib是一个非常流行的数据可视化库&#xff0c;通常用于数据的图形表示&#xff0c;也用于使用内置函数的动态图…

Java 中常用的数据结构类 API

目录 常用数据结构API 对应的线程安全的api 高可用衡量标准 常用数据结构API ArrayList: 实现了动态数组&#xff0c;允许快速随机访问元素。 import java.util.ArrayList; LinkedList: 实现了双向链表&#xff0c;适用于频繁插入和删除操作。 import java.util.LinkedLis…

小妙招:Copilot 当跳板免费调用 GPT4

GPT4 每月 20 刀&#xff0c;Github Copilot 每月 10 刀 首先叠个甲&#xff1a;免费不是 0 成本。 由于我在日常开发过程中&#xff0c;Copilot 对我来说是必需品&#xff0c;我会用它检查代码、写工具函数、写注释、干苦力。所以这钱是我的必要支出。而这篇文章是介绍如何基…

5 buuctf解题

命令执行 [BJDCTF2020]EasySearch1 打开题目 尝试弱口令&#xff0c;发现没有用 扫描一下后台&#xff0c;最后用御剑扫描到了index.php.swp 访问一下得到源码 源码如下 <?phpob_start();function get_hash(){$chars ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu…

详解 API 设计最佳实践

应用程序接口&#xff08;API&#xff09;是一种接口&#xff0c;它让应用程序可以轻松地使用另一个应用程序的数据和资源&#xff0c;API 对于一个产品或公司的成功至关重要。 如果没有 API&#xff0c;你大部分喜欢的软件今天就不会存在。例如&#xff0c;Google Maps API 可…

互联网加竞赛 机器视觉opencv答题卡识别系统

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 答题卡识别系统 - opencv python 图像识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分…

项目实战:Qt监测操作系统物理网卡通断v1.1.0(支持windows、linux、国产麒麟系统)

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/136276999 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

爬取链家二手房房价数据存入mongodb并进行分析

实验目的 1.使用python将爬虫数据存入mongodb&#xff1b; 2.使用python读取mongodb数据并进行可视化分析。 实验原理 MongoDB是文档数据库&#xff0c;采用BSON的结构来存储数据。在文档中可嵌套其他文档类型&#xff0c;使得MongoDB具有很强的数据描述能力。本节案例使用的…

第6.4章:StarRocks查询加速——Colocation Join

目录 一、StarRocks数据划分 1.1 分区 1.2 分桶 二、Colocation Join实现原理 2.1 Colocate Join概述 2.2 Colocate Join实现原理 三、应用案例 注&#xff1a;本篇文章阐述的是StarRocks-3.2版本的Colocation Join 官网文章地址&#xff1a; Colocate Join | StarRoc…

【科技素养题】少儿编程 蓝桥杯青少组科技素养题真题及解析第24套

少儿编程 蓝桥杯青少组科技素养题真题及解析第24套 1、A市和B市决定联合建造一个邮件集散中心用于将来自其他地区的邮件运送至两个城市。A 市每周会收到 4000 份邮件,B 市每周会收到 6000 份邮件。假设运送邮件的时间与集散中心距离城市的距离成正比,A市与8市之间的连线长50…

相机选型介绍

摄影测量中&#xff0c;相机是非常重要的角色&#xff0c;合适的相机产出合适的图像&#xff0c;得到合适的重建精度&#xff0c;这是相机的重要性。 您也许第一反应是&#xff0c;摄影测量所需的理想相机&#xff0c;是有着超高分辨率的相机&#xff0c;但事实可能并非如此&a…

中兴通讯携吉林移动迈向5G-A新阶段,完成3CC技术应用

日前&#xff0c;中兴通讯携手中国移动吉林移动分公司&#xff0c;在5G-A领域取得新突破。具体来说&#xff0c;双方基于MTK芯片M80终端&#xff0c;完成了5G-A三载波聚合试点&#xff0c;实测下行速率达到理论峰值4.25Gbps&#xff0c;相比2.6G单载波速率提升2.5倍。如此成绩&…

11-pytorch-使用自己的数据集测试

b站小土堆pytorch教程学习笔记 import torch import torchvision from PIL import Image from torch import nnimg_path ../imgs/dog.png imageImage.open(img_path) print(image) # imageimage.convert(RGB)transformtorchvision.transforms.Compose([torchvision.transforms.…

typecho 给文章创建目录树

受益于 shortcode 短代码插件和泽泽短代码中目录树的显示样式&#xff0c;形成了自己实现添加文章目录的思路&#xff1a; 一、文章目录树的结构 <div id"toc"><div class"toc-left"><div class"toc-btn" type"button&quo…

【机器学习基础】一元线性回归(适合初学者的保姆级文章)

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;后面的内容会越来越有意思~ &#x1f4a1;往期推荐&#xff1a; 【机器学习基础】机器学习入门&#xff08;1&#xff09; 【机器学习基…

Linux进程【补充】

文章目录 进程概念task_struct 进程创建forkvfork写时拷贝 进程状态僵尸进程孤儿进程守护进程 进程地址空间是什么为什么怎么做 进程概念 进程是一个程序的执行实例或者是担当系统资源分配的实体。当一个程序运行时&#xff0c;被从硬盘加载到内存中&#xff0c;操作系统为每个…

Canvas学习笔记02:canvas的路径扫盲,附代码案例

hello&#xff0c;我是贝格前端工场&#xff0c;最近在学习canvas&#xff0c;分享一些canvas的一些知识点笔记&#xff0c;本期分享canvas的路径知识&#xff0c;欢迎老铁们一同学习&#xff0c;欢迎关注&#xff0c;如有前端项目可以私信贝格。 一、什么是canvas路径 Canvas…

大模型 Advanced-RAG(高级检索增强生成):从理论到 LlamaIndex 实战!

最近关于检索增强生成进行了调查&#xff0c;总结了三种最近发展的范式&#xff1a; Naive RAG&#xff08;简单RAG&#xff09;Advanced RAG&#xff08;高级RAG&#xff09;Modular RAG&#xff08;模块化RAG&#xff09; 本文首先讨论这些技术&#xff0c;接着分享如何使…

数字电路 第二章—第二节(半导体二极管、三极管和MOS管的开关特性)

一、理想开关的开关特性 1、静态特性 &#xff08;1&#xff09;断开时&#xff0c;无论在多大范围内变化&#xff0c;其等效电阻&#xff0c;通过其中的电流。 &#xff08;2&#xff09;闭合时&#xff0c;无论流过其中的电流在多大范围内变化&#xff0c;其等效电阻&…