Mongoose源码剖析:mongoose的工作模型

简介:

引言

我看一个项目的时候,比较喜欢首先看它的架构和设计。因为这样在研读源码的时候,有一个指导作用,不会迷失于具体细节,并能够引导我如何去将点串成线,将线串成面。而且一个软件怎么样,很大程度上取决于它采用的架构。

本文主要介绍Mongoose的工作模型,及根据这个模型将代码大致串起来,找出主线。内容框架如下:

  • 1、线程模型
  • 2、从程序入口着手
  • 3、Mongoose的生命旅程

1、线程模型

Mongoose 采用了一个自适应的线程池的模型。有一个主线程(master thread)用于打开配置端口和等待连接的到了。一旦新的连接到来,主线程将衍生一个新的线程去服务该连接。当衍生的线程处理完连接的请求之后,它会保 持一段时间的空闲(可以通过配置选项-idle_time <seconds>控制空闲时间),在此期间主线程可能会传递另一个连接给它,让它服务。

因此,每个连接都是在自己的线程 中执行,且线程数量随着web服务器的负载而变化。然而,最大的活跃线程数由-max_thread <number>控制。如果一旦总的线程数达到了这个阈值,当新的连接到来时,主线程将等到有线程空闲时在分配线程服务新到来的连接。以此同 时,建立了TCP监听队列,即当没有线程空闲时到来的新连接会被置入该队列,当有线程空闲了会从队列中取出连接并服务。如果没有线程变空闲,而TCP队列 又满了,web服务器将拒绝新到来的连接请求。

上面所述的过程大致如下所示:

image

图1、线程模型

2、从程序入口着手

在《Mongoose源码剖析:Introduction and Installation》中,我们简单分析了Makefile文件知道生成的mongoose执行文件的入口肯定在main.c中(如果将Mongoose嵌入到你的应用程序中,就由你来决定入口了!)。在典型的main函数入口中,我们可以看到下面的流程:

main(){
启动mongoose及设置相关参数(或使用默认的);
声明几个信号的处理函数:
#ifndef _WIN32
    (void) signal(SIGCHLD, signal_handler);
#endif /* _WIN32 */
    (void) signal(SIGTERM, signal_handler);
    (void) signal(SIGINT, signal_handler);
ctx=mg_start();
process_command_line_arguments(ctx, argv);
进入死循环直到检测到程序结束标记while(exit_flag==0);
mg_stop();
}

上 面即是main函数中的主流程。需要注意的是调用mg_start()之后返回一个mg_context结构体的实例,这个实例将会在整个连接请求中用 到,而且如果你在启动mongoose中设置了参数选项,在下面的process_command_line_arguments()函数中还会对ctx 进行修改。从这里我们也知道了,mongoose程序的核心入口时mg_start(),最后终结于mg_stop()。

3、Mongoose的生命旅程

通过上面的分析我们知道Mongoose起始于mg_stop(),终结于mg_stop()。下面我们就从生命之初到生命终结之间的“故事”。说明:在这里我们不会去过于追究细节,只是串线式的把Mongoose的生命流程串起来,哪些细节或许后续的文章来解释,或者留给读者你去做了!

在mg_start()主要是做一些初始化的工作,最后才会正式进入工作服务于client。这里的初始化工作就好比一个人的出生需要十月怀胎,为诞生积蓄能量,要从受精卵长成一个完整的人。在准备工作完成之后,mg_start()会启动一个主线程master_thread,它用于监听所有的client连接请求。

启动一个主线程即启动了一个web server,在主线程中首先会将该server监听的地址(socket)加入到监听集合中去。然后一直监听该端口,只要有client的连接请求到来,它会调用accept_new_connection()去处理连接请求。

接下来,我们关注的是accept_new_connection()是如何去处理连接请求的。首先它会进行一些预判工作,决定是否允许该连接。如果允许,则调用put_socket()并将处理工作转交给它,所谓权力下放。

在 put_socket()首先也会进行一些预判工作——判断mg_context结构体的成员变量queue队列是否已满,如果满了就等待直到queue 有位置容纳请求。还有一点要说明的是:由于有可能多个client请求同时到达,对queue进行操作,所以在put_socket()中一开始就设置 (void) pthread_mutex_lock(&ctx->thr_mutex);而且请求是通过调节变量来控制等待queue是否有位置容纳请 求(void) pthread_cond_wait(&ctx->full_cond, &ctx->thr_mutex)。说了这么多准备工作,现在该正式进入工作了。至此,如果没有空闲进程且进程数量没有达到最大阈值,就会 启动一个新的工作进程worker_thread去处理client的请 求。之后就是释放信号量等资源,让其它client请求也能够请求到资源工作,如启动了一个工作进程去处理client请求,这时queue就空出一个位 置了,它会调用pthread_cond_signal(&ctx->empty_cond)让等待的client请求知道queue中有 位置了。最后就是释放put_socket()中一开始设置的锁,(void) pthread_mutex_unlock(&ctx->thr_mutex)。

到了这里,client的请求已经被分配打一个工作线程中去了。而且不同的client请求处理运行在不同的工作线程中,能够互不干扰。在worker_thread中,首先与client建立连接,只有连接上了才能为client服务。连接建立之后调用process_new_connection()去处理请求。处理完之后返回关闭连接,并通过信号机制告诉主线程,我的做工做完了。

在process_new_connection()中处理工作:首先解析请求parse_http_request(),知道请求的内容;接着就是进入Mongoose处理client请求的真正核心工作了analyze_request()。这里就不详细介绍parse_http_request()、analyze_request()是如何去解析、验证、提供具体服务的,否则就陷入了细节出不来了,这里主要是介绍Mongoose的生命之旅的主线。

下 面用图形来形象描述一下Mongoose的生命之旅,说明:该图形并不是一个精确的逻辑关系,图中的箭头方向只是描述了程序的大概流程,并都是上级调用下 级的关系,如并不是parse_http_request()调用analyze_request()等,而实际上它们都是在 process_new_connection()被调用。

image

图2、Mongoose的大概生命主线

4、总结

至此,算是介绍完了Mongoose的一个完整的工作模型了,你可以安装此主线去进行code review。只有你脑海里有这样一个模型,你就不会在研读代码是迷失了。

当然Mongoose提供的很多api,这里都没有介绍到,因为它不是本文的重点。我希望此能够带后来者步入Mongoose源码研读的大门,给后来者节省徘徊在门外停滞不前的时间。

相关文章
|
网络协议 IDE Linux
mongoose使用详细 -- 如何通过mongoose搭建服务器
mongoose使用详细 -- 如何通过mongoose搭建服务器
1003 0
|
2月前
|
监控 JavaScript 前端开发
前端的混合之路Meteor篇(六):发布订阅示例代码及如何将Meteor的响应数据映射到vue3的reactive系统
本文介绍了 Meteor 3.0 中的发布-订阅模型,详细讲解了如何在服务器端通过 `Meteor.publish` 发布数据,包括简单发布和自定义发布。客户端则通过 `Meteor.subscribe` 订阅数据,并使用 MiniMongo 实现实时数据同步。此外,还展示了如何在 Vue 3 中将 MiniMongo 的 `cursor` 转化为响应式数组,实现数据的自动更新。
|
4月前
|
缓存 前端开发 JavaScript
"React与GraphQL Apollo Client的神奇之处:如何用高效数据驱动应用让你的项目一鸣惊人?"
【8月更文挑战第31天】在当今的Web开发领域,数据驱动应用已成主流。本文章深入探讨了React——一个用于构建用户界面的流行JavaScript库,与GraphQL及Apollo Client结合使用时如何助力开发者高效创建数据驱动应用。通过示例代码,文章展示了React与GraphQL Apollo Client在实际项目中的应用方法,并总结了其优势及最佳实践,为读者提供了全面的技术指南。
44 0
|
SQL IDE PHP
Laravel Eloquent 模型 使用技巧
Laravel Eloquent 模型使用技巧
147 0
|
NoSQL JavaScript 关系型数据库
Mongoose-开篇
Mongoose 概述 • Mongoose和MySQL的Sequelize一样, 都是NodeJS中操作数据库的对象模型工具 • Mongoose使用面向对象的思想对原生的mongoDB命令进行了封装
87 0
|
存储 JSON 数据处理
最为常用的Laravel操作(1)-Eloquent模型
整理了 Laravel 框架 Eloquent 模型最常用的操作,包括一些常用的属性、方法,模型关联等。本系列共有 3 篇文章。
83 2
|
前端开发 数据库
react-admin+postgrest实现增删改查功能(摆脱接口开发)
react-admin+postgrest实现增删改查功能(摆脱接口开发)
72 0
jira学习案例67-usecallback优化异步请求2
jira学习案例67-usecallback优化异步请求2
85 0
jira学习案例67-usecallback优化异步请求2
|
SQL PHP 数据库
Laravel Eloquent 模型 进阶技巧
Laravel Eloquent 模型使用进阶技巧
150 0
|
JavaScript Python
数据工厂平台-番外:vue和django的冲突问题
数据工厂平台-番外:vue和django的冲突问题
数据工厂平台-番外:vue和django的冲突问题