赞
踩
hello !大家好呀! 欢迎大家来到我的网络编程系列之web服务器shttpd源码剖析——命令行解析,在这篇文章中,你将会学习到在Linux内核中如何创建一个自己的并发服务器shttpd,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!!
希望这篇文章能对你有所帮助,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!
目录
在 shttpd 中,命令行界面(CLI)允许用户启动、停止和控制服务器,以及配置服务器的各种参数。
shttpd 命令行的作用主要包括:
启动和停止服务器:通过命令行,可以启动或停止 shttpd 服务。
配置服务器:通过命令行参数,可以设置服务器的各种运行参数,比如端口号、文档根目录、日志文件等。
查看服务器状态:一些命令行参数可以帮助用户查看服务器的当前状态,例如查看服务器版本、查看已加载的模块等。
执行服务器维护任务:命令行还可以用来执行一些维护任务,如重载配置文件、重启服务等。
脚本化控制:命令行工具可以与脚本结合使用,允许自动执行重复性任务或集成到更复杂的工作流程中。
调试和故障排除:在服务器出现问题时,命令行工具可以用来调试和诊断问题。
在 shttpd 和其他许多 web 服务器中,文件配置是一种用来设置服务器参数和行为的机制。这些配置通常存储在一个或多个文本文件中,服务器在启动或运行时读取这些文件来决定如何处理客户端请求。文件配置的作用包括:
持久化设置:与命令行参数相比,文件配置可以将设置持久化保存,这意味着服务器重启后配置仍然有效。
方便管理:对于复杂的配置,通过文件进行设置比在命令行中输入一系列参数更为方便和直观。
模块化配置:可以将不同的配置项分门别类地放置在不同的文件中,便于管理和更新。
灵活性和可扩展性:通过修改配置文件,可以轻松地添加或修改服务器的功能,而无需重新编译服务器软件。
共享和分发:配置文件可以轻松地复制到其他服务器或共享给其他用户,从而快速设置多个服务器实例。
环境适应性:可以根据不同的运行环境(如开发、测试、生产环境)使用不同的配置文件,使服务器在不同的环境下运行。
安全性:可以通过配置文件设置用户权限、安全证书等重要安全参数,保护服务器免受未授权访问。
错误处理:配置文件可以用来定义错误处理逻辑,如自定义错误页面、日志记录等。
shttpd 的配置文件通常是简单的文本文件,其中包含了服务器的各种设置,如监听的端口、文档根目录、访问控制等。通过编辑这些配置文件,管理员可以自定义服务器的行为,以满足特定的需求。
服务器SHTTPD 可以动态配置启动参数,例如服务器的侦听端口、支持客户端并发访问的数量、超时时间的设置、访问 Web网页的路径等。采用参数配置和文件配置两种支持方式,在优先级上, 参数配置比文件配置的优先级高, 参数配置的选项值会覆盖文件配置的选项。
命令行参数配置:(我们需要启动参数是可以动态配置的)
配置文件的名称为 SHTTPD. conf, 默认路径为“/etc”下。
配置文件的格式如下:
[#注释][空格]关键字[空格]=[空格] value]
配置文件中的一行为#开头的注释或者选项配置, 不支持空行, 关键字右边的值不含有空格。各部分如下定义。
#注释: 一行以#开始表示此行为注释, 程序不对此行进行分析。
空格: 可以为0个或者多个空格。
关键字: 可以为如下的字符串, 大小写必须完全匹配。
ListenPort: 侦听端口。
MaxClient: 最大客户端并行访问数。
DocumentRoot: Web 网页根目录。
CGIRoot: CGI程序根目录。
DefaultFile: 默认访问网页名称。
TimeOut: 客户端连接空闲超时时间。
值:用户对关键字选项的配置, 全部为字符串。值中不能有引号、换行符、空格尾的空格将被解释为值的一部分), ListenPort、TimeOut等不支持十六进制的“0x”方
下面为配置文件实例。
#SHTTPD Web服务器配置文件示例 #侦听端口ListenPort=80 #最大并发访问客户端数目MaxClient=8 #Web网页根目录DocumentRoot = /home/www/ #CGI根目录CGIRoot = /home/www/cgi-bin/ #默认访问文件名DefaultFile = default. htm #客户端空闲连接超时时间TimeOut = 5
其实际过程如下图:
对于命令行参数的解析,我们使用getopt_long函数来实现快捷解析。
getopt_long
函数是 GNU C 库提供的一个函数,用于解析命令行参数。它是对标准 getopt
函数的扩展,支持长选项和短选项。长选项是指那些以两个破折号 --
开头,后面跟一个单词或多个单词的选项,而短选项则是以一个破折号 -
开头,后面跟一个字符的选项。
下面是 getopt_long
函数的基本用法和参数解释:
- #include <getopt.h>
-
- int getopt_long(int argc, char * const argv[],
- const char *optstring,
- const struct option *longopts, int *longindex);
参数说明:
argc
和 argv
:这两个参数通常直接从 main
函数的参数传递过来,分别代表命令行参数的数量和参数数组。
optstring
:这是一个字符串,用于指定程序接受哪些短选项。每个字符代表一个选项,如果选项后面跟一个冒号 :
,则表示该选项后面必须跟一个参数。
longopts
:这是一个 struct option
数组,用于指定程序接受哪些长选项。struct option
的定义如下:
- struct option {
- const char *name; // 长选项的名称,不包括 `--`
- int has_arg; // 是否有参数,可以是 no_argument, required_argument, optional_argument
- int *flag; // 如果不为 NULL,则 getopt_long 返回 0,并且将 val 的值赋给 flag 指向的变量
- int val; // 如果 flag 为 NULL,则 getopt_long 返回 val 的值
- };
longindex
:如果这个参数不为 NULL,getopt_long
会将当前长选项在 longopts
数组中的索引存储在该指针指向的位置。
getopt_long
函数的返回值是下一个选项字符,如果所有选项都解析完毕,则返回 -1
。如果遇到未知选项,则返回 ?
。
那么命令行参数中的选项定义是什么样的呢?
设置如下形式的参数来提供命令行参数选项的解析, 其中短参数类型为:
"c:d:f:ho:l:m:t:";
对应的长参数类型为:
- {"CGIRoot", required_argument, NULL, 'c'},
-
- {"ConfigFile", required_argument, NULL, 'f'},
-
- {"DefaultFile", required_argument, NULL, 'd'},
-
- {"DocumentRoot", required_argument, NULL, 'o'},
-
- {"ListenPort", required_argument, NULL, 'l'},
-
- {"MaxClient", required_argument, NULL, 'm'},
-
- {"TimeOut", required_argument, NULL, 't'},
大致过程可以用以下uml图解释:
- //start from the very beginning,and to create greatness
- //@author: Chuangwei Lin
- //@E-mail:979951191@qq.com
- //@brief: 命令行解析代码和配置文件解析的实现
-
- #include "lcw_shttpd.h"
-
- //短选项的配置为c:d:f:h:o:l:m:t:
- static char* shortopts="c:d:f:h:o:l:m:t:";
- //长选项的配置
- //option是getop_long的一个结构体参数p531
- static struct option longopts[]=
- {
- {"CGIRoot",required_argument,NULL,'c'},
- {"ConfigFile",required_argument,NULL,'f'},
- {"DefaultFile",required_argument,NULL,'d'},
- {"DocumentRoot",required_argument,NULL,'o'},
- {"ListenPort",required_argument,NULL,'l'},
- {"MaxClient",required_argument,NULL,'m'},
- {"TimeOut",required_argument,NULL,'t'},
- {"Help",no_argument,NULL,'h'},
- {0,0,0,0},
- }
- //初始化时服务器的默认配置
- extern struct conf_opts conf_para=
- {
- "/usr/local/var/www/cgi-bin/",//CGI根目录
- "index.html",//默认文件名称
- "/usr/local/var/www/",//根文件目录
- "/etc/SHTTPD.conf",//配置文件路径和名称
- 8080, //监听端口
- 4, //最大客户端数量
- 3,//超时时间
- 2//初始化线程数量
- };
- /******************************************************
- 函数名:display_usage(void)
- 参数:无
- 功能:显示参数输入方法
- *******************************************************/
- void display_usage(void)
- {
- printf("*******************Chuangwei Lin*******************\n");
- printf("sHTTPD -l number -m number -o path -c path -d filename -t seconds -o filename\n");
- printf("sHTTPD --ListenPort number\n");
- printf(" --MaxClient number\n");
- printf(" --DocumentRoot) path\n");
- printf(" --DefaultFile) filename\n");
- printf(" --CGIRoot path \n");
- printf(" --DefaultFile filename\n");
- printf(" --TimeOut seconds\n");
- printf(" --ConfigFile filename\n");
- }
- /******************************************************
- 函数名:conf_readline(int fd, char *buff, int len)
- 参数:文件描述符,缓冲区,长度
- 功能:读取配置文件的一行
- *******************************************************/
- static int conf_readline(int fd, char *buff, int len)
- {
- int n = -1;
- int i = 0;
- int begin = 0;
- memset(buff, 0, len);//清缓冲区
- for(i =0; i<len;begin?i++:i)//当开头部分不为'\r'或者'\n'时i计数
- { //begin真则i++
- n = read(fd, buff+i, 1);//读一个字符
- if(n == 0)//文件末尾
- {
- *(buff+i) = '\0';
- break;
- }
- else if(*(buff+i) == '\r' ||*(buff+i) == '\n')
- {//是回车换行
- if(begin)
- {//为一行
- *(buff+i) = '\0';
- break;
- }
- }
- else
- {
- begin = 1;
- }
- }
- return i;
- }
-
- static char* l_opt_arg;//存输入参数
- /******************************************************
- 函数名:Para_CmdParse(int argc,char* argv[])
- 参数:argc:参数个数 ,argv:参数的字符串数组,两个参数一般是从main()函数的输入参数中直接传来
- 功能:命令行解析函数,利用getopt_long函数实现
- *******************************************************/
- static int Para_CmdParse(int argc,char* argv[])
- {
- int c;
- int len;
- int value;
- //遍历输入参数,设置配置参数
- while((c=getopt_long(argc,argv,shortopts,longopts,NULL))!=-1)
- {
- switch(c)
- { //getopt_long()如果有输入参数,则输入参数为optarg
- case:'c'//CGI跟路径
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- memcpy(conf_para.CGIRoot,l_opt_arg,len+1);//更新
- }
- break;
- case:'d'//默认文件名称
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- memcpy(conf_para.DefaultFile,l_opt_arg,len+1);
- }
- break;
- case:'f'//配置文件名称和路径
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- memcpy(conf_para.ConfigFile,l_opt_arg,len+1);
- }
- break;
- case:'o'//根文件路径
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- memcpy(conf_para.DocumentRoot,l_opt_arg,len+1);
- }
- break;
- case:'l'//侦听端口
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
- if (value != LONG_MAX && value != LONG_MIN)
- {
- conf_para.ListenPort = value;//更新
- }
- }
- break;
- case:'m'//最大客户端数量
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
- if (value != LONG_MAX && value != LONG_MIN)
- {
- conf_para.MaxClient = value;//更新
- }
- }
- break;
- case:'t'//超时时间
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
- if (value != LONG_MAX && value != LONG_MIN)
- {
- conf_para.TimeOut = value;//更新
- }
- }
- break;
- case:'?'
- printf("Invalid para \n");
- case:'h'
- display_usage();
- break;
- }
- }
- }
- /******************************************************
- 函数名:Para_CmdParse(int argc,char* argv[])
- 参数:argc:参数个数 ,argv:参数的字符串数组,两个参数一般是从main()函数的输入参数中直接传来
- 功能:命令行解析函数,利用getopt_long函数实现
- *******************************************************/
- static int Para_CmdParse(int argc,char* argv[])
- {
- int c;
- int len;
- int value;
- //遍历输入参数,设置配置参数
- while((c=getopt_long(argc,argv,shortopts,longopts,NULL))!=-1)
- {
- switch(c)
- { //getopt_long()如果有输入参数,则输入参数为optarg
- case:'c'//CGI跟路径
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- memcpy(conf_para.CGIRoot,l_opt_arg,len+1);//更新
- }
- break;
- case:'d'//默认文件名称
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- memcpy(conf_para.DefaultFile,l_opt_arg,len+1);
- }
- break;
- case:'f'//配置文件名称和路径
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- memcpy(conf_para.ConfigFile,l_opt_arg,len+1);
- }
- break;
- case:'o'//根文件路径
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- memcpy(conf_para.DocumentRoot,l_opt_arg,len+1);
- }
- break;
- case:'l'//侦听端口
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
- if (value != LONG_MAX && value != LONG_MIN)
- {
- conf_para.ListenPort = value;//更新
- }
- }
- break;
- case:'m'//最大客户端数量
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
- if (value != LONG_MAX && value != LONG_MIN)
- {
- conf_para.MaxClient = value;//更新
- }
- }
- break;
- case:'t'//超时时间
- l_opt_arg = optarg;
- if (l_opt_arg && l_opt_arg[0] != ':')
- {
- len = strlen(l_opt_arg);
- value = strtol(l_opt_arg,NULL,10);//转化字符串为整形
- if (value != LONG_MAX && value != LONG_MIN)
- {
- conf_para.TimeOut = value;//更新
- }
- }
- break;
- case:'?'
- printf("Invalid para \n");
- case:'h'
- display_usage();
- break;
- }
- }
- }
- /******************************************************
- 函数名:Para_FileParse(char* file)
- 参数:文件
- 功能:文件配置解析函数
- *******************************************************/
- void Para_FileParse(char* file)
- {
- #define LINELENGTH 256
- char line[LINELENGTH];//读取缓冲区
- char *name = NULL,*value = NULL;//用于获取关键字和值
- int fd = -1;//文件描述符
- int n = 0;
- fd = open(file,O_RDONLY);//只读方式打开配置文件
- if (-1 == fd)//错误检查
- {
- goto EXITPara_FileParse;//退出
- }
- //命令格式如下
- //[#注释|[空格]关键字[空格]=[空格]value]
- //
- while((n = conf_readline(fd,line,LINELENGTH))!= 0)//每次读取一行
- {
- char *pos = line;//文件位置指针
- while(isspace(*pos))
- {
- pos++;//跳过一行开头部分的空格
- }
- if(*pos == '#')//如果是注释
- {
- continue;//那就读取下一行
- }
- name = pos;//此时的位置就是关键字的开头
- while(!isspace(*pos) && *pos != '=')//不是空格也不是’=‘,则继续读直到读完关键字
- {
- pos++;
- }
- *pos = '\0';//得到关键字
- while(isspace(*pos))//再次跳过值前面的空格
- {
- pos++;
- }
- value = pos;
- while(!isspace(*pos) && *pos != '\r' && *pos != '\n')//读到结束
- {
- pos++;
- }
- pos = '\0';//得到值
- //根据关键字,将值赋给配置文件的结构
- int ivalue;
- if(strncmp("CGIRoot",name,7))
- {
- memcpy(conf_para.CGIRoot,value,strlen(value)+1);
- }
- else if(strncmp("DefaultFile",name,11))
- {
- memcpy(conf_para.DefaultFile,value,strlen(value)+1);
- }
- else if(strncmp("DocumentRoot",name,12))
- {
- memcpy(conf_para.DocumentRoot,value,strlen(value)+1);
- }
- else if(strncmp("ListenPort",name,10))
- {
- ivalue = strtol(value,NULL,10);//转化字符串为整形
- conf_para.ListenPort = ivalue;
- }
- else if(strncmp("MaxClient",name,9))
- {
- ivalue = strtol(value,NULL,10);//转化字符串为整形
- conf_para.MaxClient = ivalue;
- }
- else if(strncmp("TimeOut",name,7))
- {
- ivalue = strtol(value,NULL,10);//转化字符串为整形
- conf_para.TimeOut = ivalue;
- }
-
- }
- close(fd);//关闭文件
- EXITPara_FileParse:
- return ;
- }
- /******************************************************
- 函数名:display_para()
- 参数:无
- 功能:显示配置的参数
- *******************************************************/
- static void display_para()
- {
- printf("*******************Chuangwei Lin*******************\n");
- printf("sHTTPD ListenPort: %d\n",conf_para.ListenPort);
- printf(" MaxClient: %d\n", conf_para.MaxClient);
- printf(" DocumentRoot: %s\n",conf_para.DocumentRoot);
- printf(" DefaultFile:%s\n",conf_para.DefaultFile);
- printf(" CGIRoot:%s \n",conf_para.CGIRoot);
- printf(" DefaultFile:%s\n",conf_para.DefaultFile);
- printf(" TimeOut:%d\n",conf_para.TimeOut);
- printf(" ConfigFile:%s\n",conf_para.ConfigFile);
- }
- /******************************************************
- 函数名:Para_Init(int argc, char *argv[])
- 参数:参数个数,和参数字符串
- 功能:初始化配置
- *******************************************************/
- void Para_Init(int argc, char *argv[])
- {
- //解析命令行输入参数
- Para_CmdParse(argc, argv);
- //解析配置文件配置参数
- if(strlen(conf_para.ConfigFile))
- {
- Para_FileParse(conf_para.ConfigFile);
- }
- display_para();
- return ;//返回配置参数
- }
好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。