当前位置:   article > 正文

【网络编程】web服务器shttpd源码剖析——命令行和文件配置解析

【网络编程】web服务器shttpd源码剖析——命令行和文件配置解析

 16b9d0dfc990426e968798e2f5a7628b.png

hello !大家好呀! 欢迎大家来到我的网络编程系列之web服务器shttpd源码剖析——命令行解析,在这篇文章中,你将会学习到在Linux内核中如何创建一个自己的并发服务器shttpd,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!!

希望这篇文章能对你有所帮助9fe07955741149f3aabeb4f503cab15a.png,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!1a2b6b564fe64bee9090c1ca15a449e3.png

03d6d5d7168e4ccb946ff0532d6eb8b9.gif               

目录

   ​编辑 一.命令行和文件配置

    ​编辑1.1 命令行是什么?

​编辑1.2 文件配置作用

​编辑二.需求分析

​编辑2.1 命令行参数需求分析

​编辑2.2 文件配置需求分析

​编辑三.具体实现

 ​编辑3.1 命令行参数设置与解析

 

​编辑3.2 文件配置参数设置与解析

​编辑四:源码分析

​编辑4.1 命令行配置以及解析设计

​编辑4.2 文件配置以及解析设计


 

   16b9d0dfc990426e968798e2f5a7628b.png 一.命令行和文件配置

    502d327b2b174a5c905179353b0e7b14.png1.1 命令行是什么?

在 shttpd 中,命令行界面(CLI)允许用户启动、停止和控制服务器,以及配置服务器的各种参数。

shttpd 命令行的作用主要包括:

  1. 启动和停止服务器:通过命令行,可以启动或停止 shttpd 服务。

  2. 配置服务器:通过命令行参数,可以设置服务器的各种运行参数,比如端口号、文档根目录、日志文件等。

  3. 查看服务器状态:一些命令行参数可以帮助用户查看服务器的当前状态,例如查看服务器版本、查看已加载的模块等。

  4. 执行服务器维护任务:命令行还可以用来执行一些维护任务,如重载配置文件、重启服务等。

  5. 脚本化控制:命令行工具可以与脚本结合使用,允许自动执行重复性任务或集成到更复杂的工作流程中。

  6. 调试和故障排除:在服务器出现问题时,命令行工具可以用来调试和诊断问题。

 

502d327b2b174a5c905179353b0e7b14.png1.2 文件配置作用

 

在 shttpd 和其他许多 web 服务器中,文件配置是一种用来设置服务器参数和行为的机制。这些配置通常存储在一个或多个文本文件中,服务器在启动或运行时读取这些文件来决定如何处理客户端请求。文件配置的作用包括:

  1. 持久化设置:与命令行参数相比,文件配置可以将设置持久化保存,这意味着服务器重启后配置仍然有效。

  2. 方便管理:对于复杂的配置,通过文件进行设置比在命令行中输入一系列参数更为方便和直观。

  3. 模块化配置:可以将不同的配置项分门别类地放置在不同的文件中,便于管理和更新。

  4. 灵活性和可扩展性:通过修改配置文件,可以轻松地添加或修改服务器的功能,而无需重新编译服务器软件。

  5. 共享和分发:配置文件可以轻松地复制到其他服务器或共享给其他用户,从而快速设置多个服务器实例。

  6. 环境适应性:可以根据不同的运行环境(如开发、测试、生产环境)使用不同的配置文件,使服务器在不同的环境下运行。

  7. 安全性:可以通过配置文件设置用户权限、安全证书等重要安全参数,保护服务器免受未授权访问。

  8. 错误处理:配置文件可以用来定义错误处理逻辑,如自定义错误页面、日志记录等。

shttpd 的配置文件通常是简单的文本文件,其中包含了服务器的各种设置,如监听的端口、文档根目录、访问控制等。通过编辑这些配置文件,管理员可以自定义服务器的行为,以满足特定的需求。

 

16b9d0dfc990426e968798e2f5a7628b.png二.需求分析

502d327b2b174a5c905179353b0e7b14.png2.1 命令行参数需求分析

服务器SHTTPD 可以动态配置启动参数,例如服务器的侦听端口、支持客户端并发访问的数量、超时时间的设置、访问 Web网页的路径等。采用参数配置和文件配置两种支持方式,在优先级上, 参数配置比文件配置的优先级高, 参数配置的选项值会覆盖文件配置的选项。

命令行参数配置:(我们需要启动参数是可以动态配置的)

4e687d51dfd64769bf278fbf570a81df.jpeg

 

502d327b2b174a5c905179353b0e7b14.png2.2 文件配置需求分析

配置文件的名称为 SHTTPD. conf, 默认路径为“/etc”下。

配置文件的格式如下:

[#注释][空格]关键字[空格]=[空格] value]

配置文件中的一行为#开头的注释或者选项配置, 不支持空行, 关键字右边的值不含有空格。各部分如下定义。

#注释: 一行以#开始表示此行为注释, 程序不对此行进行分析。

空格: 可以为0个或者多个空格。
 

关键字: 可以为如下的字符串, 大小写必须完全匹配。

ListenPort: 侦听端口。

MaxClient: 最大客户端并行访问数。

DocumentRoot: Web 网页根目录。

CGIRoot: CGI程序根目录。

DefaultFile: 默认访问网页名称。

TimeOut: 客户端连接空闲超时时间。

值:用户对关键字选项的配置, 全部为字符串。值中不能有引号、换行符、空格尾的空格将被解释为值的一部分), ListenPort、TimeOut等不支持十六进制的“0x”方

 

下面为配置文件实例。

  1. #SHTTPD Web服务器配置文件示例
  2. #侦听端口ListenPort=80
  3. #最大并发访问客户端数目MaxClient=8
  4. #Web网页根目录DocumentRoot = /home/www/
  5. #CGI根目录CGIRoot  = /home/www/cgi-bin/
  6. #默认访问文件名DefaultFile  = default. htm
  7. #客户端空闲连接超时时间TimeOut  = 5

其实际过程如下图:
 aebde7fac3734422ae3385c15dfa7f1d.jpeg

 

16b9d0dfc990426e968798e2f5a7628b.png三.具体实现

 502d327b2b174a5c905179353b0e7b14.png3.1 命令行参数设置与解析

对于命令行参数的解析,我们使用getopt_long函数来实现快捷解析。

getopt_long 函数是 GNU C 库提供的一个函数,用于解析命令行参数。它是对标准 getopt 函数的扩展,支持长选项和短选项。长选项是指那些以两个破折号 -- 开头,后面跟一个单词或多个单词的选项,而短选项则是以一个破折号 - 开头,后面跟一个字符的选项。

下面是 getopt_long 函数的基本用法和参数解释:

  1. #include <getopt.h>
  2. int getopt_long(int argc, char * const argv[],
  3. const char *optstring,
  4. const struct option *longopts, int *longindex);

参数说明:

  • argc 和 argv:这两个参数通常直接从 main 函数的参数传递过来,分别代表命令行参数的数量和参数数组。

  • optstring:这是一个字符串,用于指定程序接受哪些短选项。每个字符代表一个选项,如果选项后面跟一个冒号 :,则表示该选项后面必须跟一个参数。

  • longopts:这是一个 struct option 数组,用于指定程序接受哪些长选项。struct option 的定义如下:

    1. struct option {
    2. const char *name; // 长选项的名称,不包括 `--`
    3. int has_arg; // 是否有参数,可以是 no_argument, required_argument, optional_argument
    4. int *flag; // 如果不为 NULL,则 getopt_long 返回 0,并且将 val 的值赋给 flag 指向的变量
    5. int val; // 如果 flag 为 NULL,则 getopt_long 返回 val 的值
    6. };
  • longindex:如果这个参数不为 NULL,getopt_long 会将当前长选项在 longopts 数组中的索引存储在该指针指向的位置。

  • getopt_long 函数的返回值是下一个选项字符,如果所有选项都解析完毕,则返回 -1。如果遇到未知选项,则返回 ?

那么命令行参数中的选项定义是什么样的呢?

51fe33e687014daaa4dfe0ebc5dfa71d.jpeg

 

 设置如下形式的参数来提供命令行参数选项的解析, 其中短参数类型为:

"c:d:f:ho:l:m:t:";

对应的长参数类型为:

  1. {"CGIRoot",  required_argument, NULL'c'},
  2. {"ConfigFile",  required_argument, NULL'f'},
  3. {"DefaultFile",  required_argument, NULL'd'},
  4. {"DocumentRoot",  required_argument, NULL'o'},
  5. {"ListenPort",  required_argument, NULL'l'},
  6. {"MaxClient",  required_argument, NULL'm'},
  7. {"TimeOut",  required_argument, NULL't'},

 

502d327b2b174a5c905179353b0e7b14.png3.2 文件配置参数设置与解析

a13dc5e8d1f74812b7e57bcc8679a5ef.jpeg

 大致过程可以用以下uml图解释:

e70ea599bbd14f8e8490ff3d77a80f25.jpeg

 

16b9d0dfc990426e968798e2f5a7628b.png四:源码分析

502d327b2b174a5c905179353b0e7b14.png4.1 命令行配置以及解析设计

  1. //start from the very beginning,and to create greatness
  2. //@author: Chuangwei Lin
  3. //@E-mail:979951191@qq.com
  4. //@brief: 命令行解析代码和配置文件解析的实现
  5. #include "lcw_shttpd.h"
  6. //短选项的配置为c:d:f:h:o:l:m:t:
  7. static char* shortopts="c:d:f:h:o:l:m:t:";
  8. //长选项的配置
  9. //option是getop_long的一个结构体参数p531
  10. static struct option longopts[]=
  11. {
  12. {"CGIRoot",required_argument,NULL,'c'},
  13. {"ConfigFile",required_argument,NULL,'f'},
  14. {"DefaultFile",required_argument,NULL,'d'},
  15. {"DocumentRoot",required_argument,NULL,'o'},
  16. {"ListenPort",required_argument,NULL,'l'},
  17. {"MaxClient",required_argument,NULL,'m'},
  18. {"TimeOut",required_argument,NULL,'t'},
  19. {"Help",no_argument,NULL,'h'},
  20. {0,0,0,0},
  21. }
  22. //初始化时服务器的默认配置
  23. extern struct conf_opts conf_para=
  24. {
  25. "/usr/local/var/www/cgi-bin/",//CGI根目录
  26. "index.html",//默认文件名称
  27. "/usr/local/var/www/",//根文件目录
  28. "/etc/SHTTPD.conf",//配置文件路径和名称
  29. 8080, //监听端口
  30. 4, //最大客户端数量
  31. 3,//超时时间
  32. 2//初始化线程数量
  33. };
  34. /******************************************************
  35. 函数名:display_usage(void)
  36. 参数:无
  37. 功能:显示参数输入方法
  38. *******************************************************/
  39. void display_usage(void)
  40. {
  41. printf("*******************Chuangwei Lin*******************\n");
  42. printf("sHTTPD -l number -m number -o path -c path -d filename -t seconds -o filename\n");
  43. printf("sHTTPD --ListenPort number\n");
  44. printf(" --MaxClient number\n");
  45. printf(" --DocumentRoot) path\n");
  46. printf(" --DefaultFile) filename\n");
  47. printf(" --CGIRoot path \n");
  48. printf(" --DefaultFile filename\n");
  49. printf(" --TimeOut seconds\n");
  50. printf(" --ConfigFile filename\n");
  51. }
  52. /******************************************************
  53. 函数名:conf_readline(int fd, char *buff, int len)
  54. 参数:文件描述符,缓冲区,长度
  55. 功能:读取配置文件的一行
  56. *******************************************************/
  57. static int conf_readline(int fd, char *buff, int len)
  58. {
  59. int n = -1;
  60. int i = 0;
  61. int begin = 0;
  62. memset(buff, 0, len);//清缓冲区
  63. for(i =0; i<len;begin?i++:i)//当开头部分不为'\r'或者'\n'时i计数
  64. { //begin真则i++
  65. n = read(fd, buff+i, 1);//读一个字符
  66. if(n == 0)//文件末尾
  67. {
  68. *(buff+i) = '\0';
  69. break;
  70. }
  71. else if(*(buff+i) == '\r' ||*(buff+i) == '\n')
  72. {//是回车换行
  73. if(begin)
  74. {//为一行
  75. *(buff+i) = '\0';
  76. break;
  77. }
  78. }
  79. else
  80. {
  81. begin = 1;
  82. }
  83. }
  84. return i;
  85. }
  86. static char* l_opt_arg;//存输入参数
  87. /******************************************************
  88. 函数名:Para_CmdParse(int argc,char* argv[])
  89. 参数:argc:参数个数 ,argv:参数的字符串数组,两个参数一般是从main()函数的输入参数中直接传来
  90. 功能:命令行解析函数,利用getopt_long函数实现
  91. *******************************************************/
  92. static int Para_CmdParse(int argc,char* argv[])
  93. {
  94. int c;
  95. int len;
  96. int value;
  97. //遍历输入参数,设置配置参数
  98. while((c=getopt_long(argc,argv,shortopts,longopts,NULL))!=-1)
  99. {
  100. switch(c)
  101. { //getopt_long()如果有输入参数,则输入参数为optarg
  102. case:'c'//CGI跟路径
  103. l_opt_arg = optarg;
  104. if (l_opt_arg && l_opt_arg[0] != ':')
  105. {
  106. len = strlen(l_opt_arg);
  107. memcpy(conf_para.CGIRoot,l_opt_arg,len+1);//更新
  108. }
  109. break;
  110. case:'d'//默认文件名称
  111. l_opt_arg = optarg;
  112. if (l_opt_arg && l_opt_arg[0] != ':')
  113. {
  114. len = strlen(l_opt_arg);
  115. memcpy(conf_para.DefaultFile,l_opt_arg,len+1);
  116. }
  117. break;
  118. case:'f'//配置文件名称和路径
  119. l_opt_arg = optarg;
  120. if (l_opt_arg && l_opt_arg[0] != ':')
  121. {
  122. len = strlen(l_opt_arg);
  123. memcpy(conf_para.ConfigFile,l_opt_arg,len+1);
  124. }
  125. break;
  126. case:'o'//根文件路径
  127. l_opt_arg = optarg;
  128. if (l_opt_arg && l_opt_arg[0] != ':')
  129. {
  130. len = strlen(l_opt_arg);
  131. memcpy(conf_para.DocumentRoot,l_opt_arg,len+1);
  132. }
  133. break;
  134. case:'l'//侦听端口
  135. l_opt_arg = optarg;
  136. if (l_opt_arg && l_opt_arg[0] != ':')
  137. {
  138. len = strlen(l_opt_arg);
  139. value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
  140. if (value != LONG_MAX && value != LONG_MIN)
  141. {
  142. conf_para.ListenPort = value;//更新
  143. }
  144. }
  145. break;
  146. case:'m'//最大客户端数量
  147. l_opt_arg = optarg;
  148. if (l_opt_arg && l_opt_arg[0] != ':')
  149. {
  150. len = strlen(l_opt_arg);
  151. value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
  152. if (value != LONG_MAX && value != LONG_MIN)
  153. {
  154. conf_para.MaxClient = value;//更新
  155. }
  156. }
  157. break;
  158. case:'t'//超时时间
  159. l_opt_arg = optarg;
  160. if (l_opt_arg && l_opt_arg[0] != ':')
  161. {
  162. len = strlen(l_opt_arg);
  163. value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
  164. if (value != LONG_MAX && value != LONG_MIN)
  165. {
  166. conf_para.TimeOut = value;//更新
  167. }
  168. }
  169. break;
  170. case:'?'
  171. printf("Invalid para \n");
  172. case:'h'
  173. display_usage();
  174. break;
  175. }
  176. }
  177. }

502d327b2b174a5c905179353b0e7b14.png4.2 文件配置以及解析设计

  1. /******************************************************
  2. 函数名:Para_CmdParse(int argc,char* argv[])
  3. 参数:argc:参数个数 ,argv:参数的字符串数组,两个参数一般是从main()函数的输入参数中直接传来
  4. 功能:命令行解析函数,利用getopt_long函数实现
  5. *******************************************************/
  6. static int Para_CmdParse(int argc,char* argv[])
  7. {
  8. int c;
  9. int len;
  10. int value;
  11. //遍历输入参数,设置配置参数
  12. while((c=getopt_long(argc,argv,shortopts,longopts,NULL))!=-1)
  13. {
  14. switch(c)
  15. { //getopt_long()如果有输入参数,则输入参数为optarg
  16. case:'c'//CGI跟路径
  17. l_opt_arg = optarg;
  18. if (l_opt_arg && l_opt_arg[0] != ':')
  19. {
  20. len = strlen(l_opt_arg);
  21. memcpy(conf_para.CGIRoot,l_opt_arg,len+1);//更新
  22. }
  23. break;
  24. case:'d'//默认文件名称
  25. l_opt_arg = optarg;
  26. if (l_opt_arg && l_opt_arg[0] != ':')
  27. {
  28. len = strlen(l_opt_arg);
  29. memcpy(conf_para.DefaultFile,l_opt_arg,len+1);
  30. }
  31. break;
  32. case:'f'//配置文件名称和路径
  33. l_opt_arg = optarg;
  34. if (l_opt_arg && l_opt_arg[0] != ':')
  35. {
  36. len = strlen(l_opt_arg);
  37. memcpy(conf_para.ConfigFile,l_opt_arg,len+1);
  38. }
  39. break;
  40. case:'o'//根文件路径
  41. l_opt_arg = optarg;
  42. if (l_opt_arg && l_opt_arg[0] != ':')
  43. {
  44. len = strlen(l_opt_arg);
  45. memcpy(conf_para.DocumentRoot,l_opt_arg,len+1);
  46. }
  47. break;
  48. case:'l'//侦听端口
  49. l_opt_arg = optarg;
  50. if (l_opt_arg && l_opt_arg[0] != ':')
  51. {
  52. len = strlen(l_opt_arg);
  53. value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
  54. if (value != LONG_MAX && value != LONG_MIN)
  55. {
  56. conf_para.ListenPort = value;//更新
  57. }
  58. }
  59. break;
  60. case:'m'//最大客户端数量
  61. l_opt_arg = optarg;
  62. if (l_opt_arg && l_opt_arg[0] != ':')
  63. {
  64. len = strlen(l_opt_arg);
  65. value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
  66. if (value != LONG_MAX && value != LONG_MIN)
  67. {
  68. conf_para.MaxClient = value;//更新
  69. }
  70. }
  71. break;
  72. case:'t'//超时时间
  73. l_opt_arg = optarg;
  74. if (l_opt_arg && l_opt_arg[0] != ':')
  75. {
  76. len = strlen(l_opt_arg);
  77. value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
  78. if (value != LONG_MAX && value != LONG_MIN)
  79. {
  80. conf_para.TimeOut = value;//更新
  81. }
  82. }
  83. break;
  84. case:'?'
  85. printf("Invalid para \n");
  86. case:'h'
  87. display_usage();
  88. break;
  89. }
  90. }
  91. }
  92. /******************************************************
  93. 函数名:Para_FileParse(char* file)
  94. 参数:文件
  95. 功能:文件配置解析函数
  96. *******************************************************/
  97. void Para_FileParse(char* file)
  98. {
  99. #define LINELENGTH 256
  100. char line[LINELENGTH];//读取缓冲区
  101. char *name = NULL,*value = NULL;//用于获取关键字和值
  102. int fd = -1;//文件描述符
  103. int n = 0;
  104. fd = open(file,O_RDONLY);//只读方式打开配置文件
  105. if (-1 == fd)//错误检查
  106. {
  107. goto EXITPara_FileParse;//退出
  108. }
  109. //命令格式如下
  110. //[#注释|[空格]关键字[空格]=[空格]value]
  111. //
  112. while((n = conf_readline(fd,line,LINELENGTH))!= 0)//每次读取一行
  113. {
  114. char *pos = line;//文件位置指针
  115. while(isspace(*pos))
  116. {
  117. pos++;//跳过一行开头部分的空格
  118. }
  119. if(*pos == '#')//如果是注释
  120. {
  121. continue;//那就读取下一行
  122. }
  123. name = pos;//此时的位置就是关键字的开头
  124. while(!isspace(*pos) && *pos != '=')//不是空格也不是’=‘,则继续读直到读完关键字
  125. {
  126. pos++;
  127. }
  128. *pos = '\0';//得到关键字
  129. while(isspace(*pos))//再次跳过值前面的空格
  130. {
  131. pos++;
  132. }
  133. value = pos;
  134. while(!isspace(*pos) && *pos != '\r' && *pos != '\n')//读到结束
  135. {
  136. pos++;
  137. }
  138. pos = '\0';//得到值
  139. //根据关键字,将值赋给配置文件的结构
  140. int ivalue;
  141. if(strncmp("CGIRoot",name,7))
  142. {
  143. memcpy(conf_para.CGIRoot,value,strlen(value)+1);
  144. }
  145. else if(strncmp("DefaultFile",name,11))
  146. {
  147. memcpy(conf_para.DefaultFile,value,strlen(value)+1);
  148. }
  149. else if(strncmp("DocumentRoot",name,12))
  150. {
  151. memcpy(conf_para.DocumentRoot,value,strlen(value)+1);
  152. }
  153. else if(strncmp("ListenPort",name,10))
  154. {
  155. ivalue = strtol(value,NULL,10);//转化字符串为整形
  156. conf_para.ListenPort = ivalue;
  157. }
  158. else if(strncmp("MaxClient",name,9))
  159. {
  160. ivalue = strtol(value,NULL,10);//转化字符串为整形
  161. conf_para.MaxClient = ivalue;
  162. }
  163. else if(strncmp("TimeOut",name,7))
  164. {
  165. ivalue = strtol(value,NULL,10);//转化字符串为整形
  166. conf_para.TimeOut = ivalue;
  167. }
  168. }
  169. close(fd);//关闭文件
  170. EXITPara_FileParse:
  171. return ;
  172. }
  173. /******************************************************
  174. 函数名:display_para()
  175. 参数:无
  176. 功能:显示配置的参数
  177. *******************************************************/
  178. static void display_para()
  179. {
  180. printf("*******************Chuangwei Lin*******************\n");
  181. printf("sHTTPD ListenPort: %d\n",conf_para.ListenPort);
  182. printf(" MaxClient: %d\n", conf_para.MaxClient);
  183. printf(" DocumentRoot: %s\n",conf_para.DocumentRoot);
  184. printf(" DefaultFile:%s\n",conf_para.DefaultFile);
  185. printf(" CGIRoot:%s \n",conf_para.CGIRoot);
  186. printf(" DefaultFile:%s\n",conf_para.DefaultFile);
  187. printf(" TimeOut:%d\n",conf_para.TimeOut);
  188. printf(" ConfigFile:%s\n",conf_para.ConfigFile);
  189. }
  190. /******************************************************
  191. 函数名:Para_Init(int argc, char *argv[])
  192. 参数:参数个数,和参数字符串
  193. 功能:初始化配置
  194. *******************************************************/
  195. void Para_Init(int argc, char *argv[])
  196. {
  197. //解析命令行输入参数
  198. Para_CmdParse(argc, argv);
  199. //解析配置文件配置参数
  200. if(strlen(conf_para.ConfigFile))
  201. {
  202. Para_FileParse(conf_para.ConfigFile);
  203. }
  204. display_para();
  205. return ;//返回配置参数
  206. }

   好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦4d7d9707063b4d9c90ac2bca034b5705.png!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟!2cd0d6ee4ef84605933ed7c04d71cfef.jpeg  

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/463259
推荐阅读
相关标签
  

闽ICP备14008679号