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 IPv4
  struct mg_dns dns6;           // DNS for IPv6
  int dnstimeout;               // DNS resolve timeout in milliseconds
  bool use_dns6;                // Use DNS6 server by default, see #1532
  unsigned long nextid;         // Next connection ID,该数字逐渐增大,不会减小 ?
  unsigned long timerid;        // Next timer ID
  void *userdata;               // Arbitrary user data pointer
  void *tls_ctx;                // TLS context shared by all TLS sessions
  uint16_t mqtt_id;             // MQTT IDs for pub/sub
  void *active_dns_requests;    // DNS requests in progress
  struct mg_timer *timers;      // Active timers
  int epoll_fd;                 // Used when MG_EPOLL_ENABLE=1
  void *priv;                   // Used by the MIP stack
  size_t extraconnsize;         // Used by the MIP stack
  MG_SOCKET_TYPE pipe;          // Socketpair end for mg_wakeup()
#if MG_ENABLE_FREERTOS_TCP
  SocketSet_t ss;  // NOTE(lsm): referenced from socket struct
#endif
};

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

struct mg_connection {
  struct mg_connection *next;  // 指向下一个client
  struct mg_mgr *mgr;          // Our container
  struct mg_addr loc;          // host地址信息
  struct mg_addr rem;          // client地址信息
  void *fd;                    // Connected socket, or LWIP data
  unsigned long id;            // Auto-incrementing unique connection ID,给client的唯一id,但不一定连续不知道有什么含义
  struct mg_iobuf recv;        // Incoming data
  struct mg_iobuf send;        // Outgoing data
  struct mg_iobuf prof;        // Profile data enabled by MG_ENABLE_PROFILE
  struct mg_iobuf rtls;        // TLS only. Incoming encrypted data
  mg_event_handler_t fn;       // User-specified event handler function,在main中,cb
  void *fn_data;               // User-specified function parameter
  mg_event_handler_t pfn;      // Protocol-specific handler function,处理协议的函数,如http_cb
  void *pfn_data;              // Protocol-specific function parameter
  char data[MG_DATA_SIZE];     // Arbitrary connection data
  void *tls;                   // TLS specific data
  // 位域,下面这么多总共占4个字节
  unsigned is_listening : 1;   // Listening connection, 是否为监听fd
  unsigned 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 progress
  unsigned is_arplooking : 1;  // Non-blocking ARP resolution is in progress
  unsigned is_connecting : 1;  // Non-blocking connect is in progress
  unsigned is_tls : 1;         // TLS-enabled connection
  unsigned is_tls_hs : 1;      // TLS handshake is in progress
  unsigned is_udp : 1;         // UDP connection
  unsigned is_websocket : 1;   // WebSocket connection
  unsigned is_mqtt5 : 1;       // For MQTT connection, v5 indicator
  unsigned is_hexdumping : 1;  // Hexdump in/out traffic
  unsigned is_draining : 1;    // Send remaining data, then close and free
  unsigned is_closing : 1;     // Close and free the connection immediately
  unsigned is_full : 1;        // Stop reads, until cleared
  unsigned is_resp : 1;        // Response is still being generated,生在生成c->send
  unsigned is_readable : 1;    // Connection is ready to read
  unsigned is_writable : 1;    // Connection is ready to write
};

struct mg_http_message
存储解析后的http信息

struct mg_str {
  const char *ptr;  // Pointer to string data
  size_t len;       // String len
};

struct mg_http_header {
  struct mg_str name;   // Header name
  struct mg_str value;  // Header value
};

struct mg_http_message {
  struct mg_str method, uri, query, proto;             // Request/response line
  struct mg_http_header headers[MG_MAX_HTTP_HEADERS];  // Headers  MG_MAX_HTTP_HEADERS=30
  struct mg_str body;                                  // Body
  struct mg_str head;                                  // Request + headers
  struct 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_CLOEXEC
    c->fd = S2PTR(fd);
    MG_EPOLL_ADD(c);  // 加入epoll
    success = 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中这里几乎一直处于0
      if (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就触发EPOLLOUT
    if (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->rem
    LIST_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_CLOEXEC
    setsockopts(c);                   // setsockopt
    c->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 -3
    iolog(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 -3
  
  iolog(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.mfbz.cn/a/408450.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【网络编程】okhttp源码解析

文章目录 配置清单框架结构解析 配置清单 首先了解一下okHttp的配置清单&#xff1a; Dispatcher dispatcher &#xff1a;调度器&#xff0c;⽤于调度后台发起的⽹络请求&#xff0c;有后台总请求数和单主机总请求数的控制。List<Protocol> protocols &#xff1a;⽀持…

idea 打jar包、lib文件夹

idea目录文件 idea四层级结构 idea操作Java文件的基本单位&#xff1a;项目&#xff08;Project&#xff09;。对应四级结构 第1层级架构&#xff1a;项目&#xff08;project&#xff09; 在 IntelliJ IDEA 中Project是最顶级的结构单元&#xff0c;然后就是Module&#xf…

[HTML]Web前端开发技术30(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页

希望你开心,希望你健康,希望你幸福,希望你点赞! 最后的最后,关注喵,关注喵,关注喵,佬佬会看到更多有趣的博客哦!!! 喵喵喵,你对我真的很重要! 目录 前言 网页标题:手机批发业务-商品备选区<

PyTorch概述(五)---LINEAR

torch.nn.Linear torch.nn.Linear(in_features,out_features,biasTrue,deviceNone,dtypeNone) 对输入的数据应用一个线性变换&#xff1a; 该模块支持TensorFLoat32类型的数据&#xff1b;在某些ROCm设备上&#xff0c;使用float16类型的数据输入时&#xff0c;该模块在反向传…

3.openEuler物理存储及逻辑卷管理(一):磁盘存储挂载与使用

openEuler OECA认证辅导,标红的文字为学习重点和考点。 如果需要做实验,建议下载麒麟信安、银河麒麟、统信等具有图形化的操作系统,其安装与openeuler基本一致。 磁盘大类: HDD, (Hard Disk Drive的缩写) : 由一个或者多个铝制或者玻璃制成的磁性碟 片,磁头,…

消息中间件篇之RabbitMQ-高可用机制

一、怎么保证高可用性 在生产环境下&#xff0c;使用集群来保证高可用性&#xff0c;一般我们采用普通集群、镜像集群、仲裁队列。 二、普通集群 普通集群&#xff0c;或者叫标准集群&#xff08;classic cluster&#xff09;&#xff0c;具备下列特征&#xff1a; 1. 会在集…

LabVIEW串口通信的激光器模块智能控制

LabVIEW串口通信的激光器模块智能控制 介绍了通过于LabVIEW的VISA串口通信技术在激光器模块控制中的应用。通过研究VISA串口通信的方法和流程&#xff0c;实现了对激光器模块的有效控制&#xff0c;解决了数据发送格式的匹配问题&#xff0c;为激光器模块的智能控制提供了一种…

科学高效备考2024年AMC10:2000-2023年1250道AMC10真题练一练

我整理了2000-2023年的全部AMC10的AB卷真题共1250题&#xff0c;并且独家制作了多种在线练习&#xff0c;利用碎片化时间&#xff0c;一年足以通过自学在2024年AMC10竞赛中取得好成绩。 我们今天继续来随机看五道题目和解析。 2000-2023年AMC10真题练一练&#xff1a;2013年第…

一台台式电脑的耗电量有多少瓦?你知道吗?

核实后将予以处理。 感谢您为社区和谐做出的贡献。 一般来说&#xff0c;大多数台式电脑的功率在250W左右&#xff0c;也就是每4小时耗一度电。 一般有每小时100W左右的低功耗计算机&#xff0c;也有每小时1000W左右的高功耗计算机。 对于笔记本电脑来说&#xff0c;每小时约为…

990-03产品经理与程序员:什么是 IT 与业务协调以及为什么它很重要?

What is IT-business alignment and why is it important? 什么是IT-业务一致性&#xff1f;为什么它很重要&#xff1f; It’s more important than ever that IT and the business operate from the same playbook(剧本). So why do so many organizations struggle to ach…

3分钟彻底搞懂什么是 token

几年前在一次工作中&#xff0c;第一次接触到自然语言处理模型 BERT。 当时在评估这个模型的性能时&#xff0c;领导说这个模型的性能需要达到了 200 token 每秒&#xff0c;虽然知道这是一个性能指标&#xff0c;但是对 token 这个概念却不是很清晰。 因为当时接触视觉模型多…

快速启动-后台管理系统

目录 Gitee人人开源 后端快速启动 1.clone仓库到本地 2.初始化数据库 3.更改数据库连接 4.启动项目验证 前端快速启动 1.克隆仓库 2.vsCode打开 3.控制台npm install 4.验证测试 时代已然不同&#xff0c;后台管理也可以使用脚手架方式快速启动。 Gitee人人开源 地…

使用 ES|QL 优化可观察性:简化 Kubernetes 和 OTel 的 SRE 操作和问题解决

作者&#xff1a;Bahubali Shetti 作为一名运营工程师&#xff08;SRE、IT 运营、DevOps&#xff09;&#xff0c;管理技术和数据蔓延是一项持续的挑战。 简单地管理大量高维和高基数数据是令人难以承受的。 作为单一平台&#xff0c;Elastic 帮助 SRE 将无限的遥测数据&#…

谷歌连发 Gemini1.5、Gemma两种大模型,Groq让模型输出速度快18倍

本周&#xff0c;我们观察到以下AI领域的新动向和新趋势&#xff1a; 1.谷歌连发Gemini1.5和Gemma两种大模型&#xff0c; 其中Gemini1.5采用MoE架构&#xff0c;并拥有100万token上下文长度&#xff0c;相比Gemini 1.0性能大幅提升。Gemma是谷歌新推出的开源模型&#xff0c;…

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

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

32单片机基础:对射式红外传感器计次

接线如下图&#xff1a; 在HardWare建立两个文件&#xff1a;如图 COuntSensor.c 如何配置外部中断,根据下面图&#xff0c;我们需要把外部中断从GPIO到NVIC这一路出现的外设模块都配置好。把这条信号打通就OK了。 1.配置RCC:把我们这里涉及的外设时钟都打开&#xff0c;不打…

[算法沉淀记录] 排序算法 —— 冒泡排序

排序算法 —— 冒泡排序 基本概念 冒泡排序是一种简单的排序算法。它重复地遍历要排序的列表&#xff0c;一次比较两个元素&#xff0c;并交换它们的位置&#xff0c;如果它们不是按照升序排列的。这步遍历是重复进行的&#xff0c;直到没有再需要交换&#xff0c;也就是说该…

【MATLAB】 LMD信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 LMD分解算法 LMD (Local Mean Decomposition) 分解算法是一种信号分解算法&#xff0c;它可以将一个信号分解成多个局部平滑的成分&#xff0c;并且可以将高频噪声和低频信号有效地分离出来。LMD 分解算…

消息中间件篇之RabbitMQ-消息不丢失

一、生产者确认机制 RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。消息发送到MQ以后&#xff0c;会返回一个结果给发送者&#xff0c;表示消息是否处理成功。 当消息没有到交换机就失败了&#xff0c;就会返回publish-confirm。当消息没有到达MQ时&…

打开 Camera app 出图,前几帧图像偏暗、偏色该怎样去避免?

1、问题背景 使用的安卓平台&#xff0c;客户的应用是要尽可能快的获取到1帧图像效果正常的图片。 但当打开 camera 启动出流后&#xff0c;前3-5帧图像是偏暗、偏色的&#xff0c;如下图所示&#xff0c;是抓取出流的前25帧图像&#xff0c; 前3帧颜色是偏蓝的&#xff0c;…