DICOM:剖析Orthanc中的Web Server,Mongoose之 Flag bit & Event(三)

背景:

Orthanc是本专栏中介绍过的一款新型DICOM服务器,具有轻量级、支持REST的特性,可将任意运行Windows和Linux系统的计算机变成DICOM服务器,即miniPACS。Orthanc内嵌多种模块,数据库管理简单,且不依赖于第三方软件。因此通过剖析Orthanc源码可以学习到搭建DICOM系统中的各个环节,例如SQLite嵌入型数据库、GoogleLog日志库、DCMTK医学DICOM库,以及近期要介绍的开源Web Server,Mongoose。

上一篇博文中简单的分析了Mongoose中连接请求触发的事件序列,调试输出结果大致上符合Fossa官网给出的*NS_ACCEPT->(NS_RECV->NS_SEND->NS_POLL…)->NS_CLOSE 流程,但是实际运行时刻由于网络环境实时变化,因此输出的调试日志中偶尔会出现多次NS_ACCEPT或者多次NS_POLL等等。博文末尾提到要想了解事件触发的真正原因,需要分析ns_poll_server函数源码,接下来通过分析Mongoose和Fossa的设计来对事件触发有一个更全面的了解。*

Mongoose事件

官方说明文档中对于Mongoose的描述是:

Mongosoe has single-threaded, event-driven, asynchronous, non-blocking core. 这个内核就是Fossa。这其中单线程(single-threaded)指的就是主线程中的mg_poll_server循环。

mg_poll_server函数内部遍历所有的有效连接,通过 select 异步方式监控各连接套接字来完成一次IO迭代操作。如此反复直至处理完毕。每当select返回,针对状态发生变化的套接字(有数据要发送或接收)进行IO操作。但是mg_poll_server本身并不完成循环遍历,需要外部循环调用mg_poll_server来实现实时监控连接状态。

查看代码的话发现mg_poll_server内部就是简单的调用了ns_mgr_poll函数,那么我们看一下Fossa对该函数的描述:

Fossa是一个支持多种协议的网络库,实现了非阻塞、异步IO处理,提供基于事件的API。Fossa的使用方式是,先声明并初始化事件处理程序,创建连接;最后通过循环调用ns_mgr_poll函数实现事件监控。ns_mgr_poll迭代遍历所有套接字,接收新连接、发送和接收数据、关闭连接,并根据 具体事件 调用相应的 事件处理函数

Fossa中要求每个连接需要绑定事件处理函数,即event handler function,由用户自定义实现。事件处理是Fossa应用的核心元素——设定了程序的功能。Mongoose就是对Fossa的一次封装,规定了各种事件的默认处理程序,因此直接复制粘贴Mongoose官方文档中的示例代码就可以开启一个简单的Web Server,具体代码如下:

#include "mongoose.h"
int main(void) {

  struct mg_server *server = mg_create_server(NULL, NULL);
  mg_set_option(server, "document_root", ".");  // Serve current directory
  mg_set_option(server, "listening_port", "8080");  // Open port 8080

  for (;;) {
    mg_poll_server(server, 1000);   // Infinite loop, Ctrl-C to stop
  }
  mg_destroy_server(&server);

  return 0;
}

上述代码中并未像Fossa官网所述,给出用户自定义的事件处理函数却能顺利开启Web Server( 详情可参考博文DICOM:剖析Orthanc中的Web Server, Mongoose ),这恰恰说明了Mongoose在对Fossa进行封装时给出了默认的事件处理函数,即mg_ev_handler,在该函数内部规定了Fossa中各种事件的处理流程,由于代码过长此处就不贴出来了,详情可参考Link:mg_ ev_handler。所以在使用Mongoose时主要关注的是自定义事件,另外Mongoose对Fossa的事件进行了简单的再封装,以MG_开头来标记事件,诸如MG_AUTH、MG_REQUEST、MG_CONNECT、MG_REPLY。

Fossa标志

由上面介绍了解到Mongoose重点是对Fossa的自定义事件进行二次封装,其主要贡献是设计了Fossa事件的默认处理流程函数,即上面提到的mg_ev_handler。因此要想解决上一篇博文中的疑问事件真正触发的原因是什么? 。通过分析mg_ev_handler源码只能是了解了Fossa事件的触发机制,并未真正了解原因。因此要想解决疑惑,需要分析Fossa的处理核心,即ns_mgr_poll 。在Fossa中对于每一个连接都包含相应的flag bit field,即标志位、状态位或特征位。而flag bit目的就是用于区别连接(这里的连接是名词,代表所有与Fossa相关的请求。在Fossa中将连接分为三类,即Inbound、Outbound和Listening)整个生命周期所处的不同阶段,对每个阶段用一个flag来表示。 ns_mgr_poll正是根据flag bit来分情况处理各种连接,比如添加新连接、开始读取数据、开始发送数据、关闭连接等等;也正是由于flag bit将连接的各个阶段区分开来,才使得能够将不同阶段的处理进行模式化,也就是事件化

所以要想搞清楚之前博文中事件的触发流程,根本是需要了解Fossa和Mongoose是如何用flag bit来表示连接的各个阶段的。Fossa官方文档指出每个连接都有标志位域。Fossa针对不同协议定义了多种标志,其中一些标志由Fossa来设置,部分标志需要由外部用户自定义的事件处理函数来设置(以此来完成用户与Fossa的交互)。下面列举主要的标志:

  • NSF_FINISHED_SENDING_DATA
  • NSF_BUFFER_BUT_DONT_SEND
  • NSF_CLOSE_IMMEDIATELY
  • NSF_USER1/NSF_USER2/NSF_USER3/NSF_USER4

以上标志位都是由外部用户自定义的事件处理函数来设置的,下面看一下Fossa内部设置的标志位,

  • NSF_SSL_HANDSHAKE_DOWN
  • NSF_CONNECTING
  • NSF_LISTENING
  • NSF_WEBSOCKET_NO_DEFRAG
  • NSF_IS_WEBSOCKET

根据标志位名称大致猜测出标志位是跟连接建立过程连接具体状态 相关,由此可知标志位域(flag bit field) 关乎http web server的处理流程,是Fossa和Mongoose开源库内部的核心逻辑,所以需要Fossa内部自己实现——开源库中往往都会将协议规定的流程化部分自己实现,只将可定制化部分交由用户自定义。

找准了问题入手的方向,下面就以Mongoose官方文档为例进行实例测试:

实例测试

为了方便查看,再一次将官方安装说明中的代码贴在此处,如下:

#include "mongoose.h"
int main(void) {

  struct mg_server *server = mg_create_server(NULL, NULL);
  mg_set_option(server, "document_root", ".");  // Serve current directory
  mg_set_option(server, "listening_port", "8080");  // Open port 8080

  for (;;) {
    mg_poll_server(server, 1000);   // Infinite loop, Ctrl-C to stop
  }
  mg_destroy_server(&server);

  return 0;
}

另外为了跟踪Fossa总ns_mgr_poll函数中各阶段标志位的情况,对mongoose.c中的ns_mgr_poll代码修改,添加相应的调试输出信息。具体修改如下:

  time_t ns_mgr_poll(struct ns_mgr *mgr, int milli) {
  int loop=0;

  struct ns_connection *conn, *tmp_conn;
  struct timeval tv;
  fd_set read_set, write_set;
  sock_t max_fd = INVALID_SOCKET;
  time_t current_time = time(NULL);

  FD_ZERO(&read_set);
  FD_ZERO(&write_set);
  ns_add_to_set(mgr->ctl[1], &read_set, &max_fd);

  for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) {
      printf("The for loop in adding sock or conn section is %d times\n",loop++);//Just for debugging
tmp_conn = conn->next;
if (!(conn->flags & (NSF_LISTENING | NSF_CONNECTING))) {
        printf("For the Flag --%d-- ,For the sock --%d--,Call user ev_handler for NS_POLL\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  ns_call(conn, NS_POLL, &current_time);
}
if (!(conn->flags & NSF_WANT_WRITE)) {
  //DBG(("%p read_set", conn));
        printf("For the Flag --%d--,For the sock --%d--, call ns_add_to_set function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  ns_add_to_set(conn->sock, &read_set, &max_fd);
}
if (((conn->flags & NSF_CONNECTING) && !(conn->flags & NSF_WANT_READ)) ||
(conn->send_iobuf.len > 0 && !(conn->flags & NSF_CONNECTING) &&
 !(conn->flags & NSF_BUFFER_BUT_DONT_SEND))) {
  //DBG(("%p write_set", conn));
             printf("For the Flag --%d--2--,For the sock --%d-- call ns_add_to_set function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  ns_add_to_set(conn->sock, &write_set, &max_fd);
}
if (conn->flags & NSF_CLOSE_IMMEDIATELY) {
        printf("For the Flag --%d--, For the sock --%d-- call ns_close_conn function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  ns_close_conn(conn);
}
  }

  tv.tv_sec = milli / 1000;
  tv.tv_usec = (milli % 1000) * 1000;
  loop=0;
  if (select((int) max_fd + 1, &read_set, &write_set, NULL, &tv) > 0) {
// select() might have been waiting for a long time, reset current_time
// now to prevent last_io_time being set to the past.
current_time = time(NULL);

// Read wakeup messages
if (mgr->ctl[1] != INVALID_SOCKET &&
FD_ISSET(mgr->ctl[1], &read_set)) {
  struct ctl_msg ctl_msg;
  int len = (int) recv(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0);
  send(mgr->ctl[1], ctl_msg.message, 1, 0);
  if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) {
struct ns_connection *c;
for (c = ns_next(mgr, NULL); c != NULL; c = ns_next(mgr, c)) {
  ctl_msg.callback(c, NS_POLL, ctl_msg.message);
}
  }
};
for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) {
    printf("The for loop in select section is %d times\n",loop++);
  tmp_conn = conn->next;
  if (FD_ISSET(conn->sock, &read_set)) {
if (conn->flags & NSF_LISTENING) {
            printf("For the Flag --%d--, For the sock --%d--, NSF_LISTENING!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  if (conn->flags & NSF_UDP) {
              printf("For the Flag --%d--,For the sock --%d--, call ns_handler_udp function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
ns_handle_udp(conn);
  } else {
// We‘re not looping here, and accepting just one connection at
// a time. The reason is that eCos does not respect non-blocking
// flag on a listening socket and hangs in a loop.
              printf("For the Flag --%d--,For the sock --%d-- call accept_conn function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
accept_conn(conn);
  }
} else {
  conn->last_io_time = current_time;
          printf("For the Flag --%d--,For the sock --%d-- call ns_read_from_socket function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  ns_read_from_socket(conn);
}
  }

  if (FD_ISSET(conn->sock, &write_set)) {
if (conn->flags & NSF_CONNECTING) {
            printf("For the Flag --%d--,For the sock --%d-- call ns_read_from_socket function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  ns_read_from_socket(conn);
} else if (!(conn->flags & NSF_BUFFER_BUT_DONT_SEND)) {
  conn->last_io_time = current_time;
          printf("For the Flag --%d--,For the sock --%d-- call ns_write_to_socket function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  ns_write_to_socket(conn);
}
  }
}
  }
  loop=0;
  for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) {
      printf("The for loop in the close section is %d times\n",loop++);
tmp_conn = conn->next;
if ((conn->flags & NSF_CLOSE_IMMEDIATELY) ||
(conn->send_iobuf.len == 0 &&
  (conn->flags & NSF_FINISHED_SENDING_DATA))) {
              printf("For the Flag --%d--2--,For the sock --%d-- call ns_close_conn function!\n",conn->flags,(conn->sock!=NULL?conn->sock:-1));//Just for debugging
  ns_close_conn(conn);
}
  }

  return current_time;
}

【注】:代码中的printf输出语句就是为了方便调试添加的,待测试完毕请自行删除,以免影响Mongoose服务器性能。

测试结果

ns_mgr_poll函数内部结构可知,函数内部主要分成三大功能模块:

1. 链接配置阶段(即添加新连接到服务端链表,及设置对各个连接的可读或可写性检查)

2. 链接监控阶段(利用select异步模型监控各连接读写状态)

3. 链接清理阶段(根据连接实际状态查看是否需要关闭)

在调试信息中的结果与上述三大类一一对应,为了方便查看调试结果对不同的调试信息使用了不同的背景色。如下图所示:

该图表明Mongoose Web Server初始化完成后的状态,此刻mg_server的连接链表中只包含初始化时的监听端口,即listening connection

接下来在浏览器中输入http://localhost:8080,回车后调试日志结果如下图:

从图中我们可以看出原本的listening connection在select时检测到有新的链接接入,即Inbound connection。在链接配置阶段将新接受的Inbound添加到服务端的连接链表中,且插入位置为表头,由下一阶段链接监控阶段的for循环输出日志可以确定插入位置是表头。

对于数据的接收和发送,Fossa内部使用了缓冲机制,其缓冲结构如下图所示:

待数据接收和发送处理完成后,起初Mongoose服务的连接链表中还保存三个连接,随着时间的推移,除了listening connection以外的两个连接逐个关闭,Mongoose Web Server又恢复到初始化状态。

从上面的调试日志中可以看出在ns_mgr_poll函数内部的三大模块中主要是根据连接链表中各连接的flag bit来进行分类处理,实现端口监控连接接入接收和发送数据连接关闭等功能。这也正是我们上篇博文中希望深入研究的部分,此次博文中只是分析调试了Mongoose官网的测试实例,至于原理性的东西可能要牵扯到HTTP协议和具体的实现时序图等内容,具体细节会在后续文章中给出,敬请期待。

第一次使用MarkDown写CSDN博文,不知道效果如何,^_^

作者:[email protected]

时间:2015-02-10

时间: 2024-10-13 10:07:27

DICOM:剖析Orthanc中的Web Server,Mongoose之 Flag bit & Event(三)的相关文章

DICOM:剖析Orthanc中的Web Server, Mongoose

背景: DICOM专栏文章中介绍过解构PACS(分布式PACS)Orthanc的安装和使用,以及相关插件.SQLite数据库等主要模块的分析,此次简单介绍Orthanc中嵌入的Web Server,Mongoose.依托于Mongoose这一轻量级Web Server,Orthanc很好的实现了RESTful API与传统DICOM服务的整合,这也是实现分布式PACS的关键.博文中先给出Mongoose的安装和简单的与C/C++的嵌入式编程,让大家对Mongoose有一个初步的认识,后续再深入分

DICOM:剖析Orthanc中的Web Server,Mongoose之“连接请求触发的事件序列”(二)

背景: Orthanc是本专栏中介绍过的一款新型DICOM服务器,具有轻量级.支持REST的特性,可将任意运行Windows和Linux系统的计算机变成DICOM服务器,即miniPACS.Orthanc内嵌多种模块,数据库管理简单,且不依赖于第三方软件.因此通过剖析Orthanc源码可以学习到搭建DICOM系统中的各个环节,例如SQLite嵌入型数据库.GoogleLog日志库.DCMTK医学DICOM库,以及近期要介绍的开源Web Server,Mongoose. 题记: 近期计划参照官网剖

ubuntu16.04-x64系统中Jexus web server部署.NetCore和端口分析引发的猜想!

您有这样的牢骚么? 有一周没更新博客了,简单说下在干什么吧:主要是公司安排对接某旅游大公司的接口,接口数量倒也就10个左右,对接完后还需要加入到业务系统中和App端,因此还是需要花点时间的:时间上来说业务需求安排在6月最后一周上线,整个3周的时间,就本人一人负责,由于在这之前对接过另外一个公司接口,我已经搭建好了整体架构和提供给app端接口了,因此主要还是对接某公司接口而已,至于细节上的东西改改后台系统,调调数据格式应该就差不多了:就本人开发的熟练度来讲一周时间基本能搞定,其他剩余时间就测试,喝

转载的web server实例

asp.net—web server模拟网上购物 2014-05-08     我来说两句   来源:asp.net—web server模拟网上购物   收藏    我要投稿 在学vb的时候学到了api函数,今天学习asp.net中的web server,web server和api函数一样都是为用户提供了一个接口,客户端可以在远程直接调用,不需要知道它具体的算法,难易程度,可以直接使用方法. 一.基础 概念: 1.web服务是应用程序 2.它向外界暴露了一个能够通过web进行调用的api 3

DICOM医学图像处理:深入剖析Orthanc的SQLite,了解WADO & RESTful API

背景: 上一篇博文简单翻译了Orthanc官网给出的CodeProject上"利用Orthanc Plugin SDK开发WADO插件"的博文,其中提到了Orthanc从0.8.0版本之后支持快速查询,而原本的WADO请求需要是直接借助于Orthanc内部的REST API逐级定位.那么为什么之前的Orthanc必须要逐级来定位WADO请求的Instance呢?新版本中又是如何进行改进的呢?此篇博文通过分析Orthanc内嵌的SQLite数据库,来剖析Orthanc的RESTful A

DICOM:深入剖析Orthanc的SQLite,了解WADO&RESTful API

背景: 上一篇博文简单翻译了Orthanc官网给出的CodeProject上"利用Orthanc Plugin SDK开发WADO插件"的博文,其中提到了Orthanc从0.8.0版本之后支持快速查询,而原本的WADO请求需要是直接借助于Orthanc内部的REST API逐级定位.那么为什么之前的Orthanc必须要逐级来定位WADO请求的Instance呢?新版本中又是如何进行改进的呢?此篇博文通过分析Orthanc内嵌的SQLite数据库,来剖析Orthanc的RESTful A

web server(protein protection )搭建过程中遇到的问题

1.StringBuffer中append方法有错误 原因分析:是project默认的JRE系统库和配置的jre不匹配. 解决方法:项目属性->Add Libray->JRE System Library->Alternate JRE(选择Sun JDK 1.6.0_13) 2.文件上传问题 mySmartUpload.setAllowedFilesList("txt");  //设置上传文件类型只能是txt格式 文件上传处理过程 jsp文件: <div id=

在nginx中配置如何防止直接用ip访问服务器web server及server_name特性讲解

看了很多nginx的配置,好像都忽略了ip直接访问web的问题,不利于SEO优化,所以我们希望可以避免直接用IP访问网站,而是域名访问,具体怎么做呢,看下面. 官方文档中提供的方法: If you do not want to process requests with undefined “Host” header lines, you may define a default server that just drops the requests: server { listen 80 de

将Eclipse中的Web项目部署到Tomcat与修改Tomcat服务器Server Locations

1.将Eclipse中的Web项目部署到Tomcat 记得开始学习JavaWeb的时候,首先用的是Eclipse开发,但是有一个问题始终没有弄明白,做好的Web项目是如何发布到Tomcat服务器上的呢?最后得到了一个结论,那就是Eclipse这个软件可能是将项目发布到了一个临时的目录,只有打开Eclipse的时候,启动服务器才能运行Web项目.也正是因为这个原因,一直用的都是MyEclipse开发Java Web项目(十分的方便和顺手).这几天用的比较多,因此又对这个问题产生了兴趣,能不能将做好