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

 

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

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

               

目录

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

    ​编辑1.1 命令行是什么?

​编辑1.2 文件配置作用

​编辑二.需求分析

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

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

​编辑三.具体实现

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

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

​编辑四:源码分析

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

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


    一.命令行和文件配置

    1.1 命令行是什么?

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

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

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

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

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

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

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

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

1.2 文件配置作用

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

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

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

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

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

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

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

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

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

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

二.需求分析

2.1 命令行参数需求分析

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

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

2.2 文件配置需求分析

配置文件的名称为 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

其实际过程如下图:
 

三.具体实现

 3.1 命令行参数设置与解析

对于命令行参数的解析,我们使用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'},

3.2 文件配置参数设置与解析

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

四:源码分析

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

//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;
        }
    }
} 

4.2 文件配置以及解析设计

/******************************************************
函数名: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 ;//返回配置参数
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/552283.html

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

相关文章

WAF攻防-权限控制代码免杀异或运算变量覆盖混淆加密传参

知识点 1、脚本后门基础&原理 2、脚本后门查杀绕过机制 3、权限维持-覆盖&传参&加密&异或等 章节点&#xff1a; WAF绕过主要集中在信息收集&#xff0c;漏洞发现&#xff0c;漏洞利用&#xff0c;权限控制四个阶段。 代码表面层免杀-ASP&PHP&JSP&a…

Element入门

安装ElementUI组件库 npm install element-ui2.15.3 引入 import Vue from vue; import ElementUI from element-ui; import element-ui/lib/theme-chalk/index.css; import App from ./App.vue;Vue.use(ElementUI);new Vue({el: #app,render: h > h(App) }); 应用 模板 …

C++笔记:异常

文章目录 C 运行时错误处理机制及其不足之处C 异常概念异常的使用异常的抛出和匹配原则在函数调用链中异常栈展开匹配原则异常的重新抛出举例演示说明例子一&#xff1a;串联举例演示大部分原则例子二&#xff1a;模拟服务器开发中常用的异常继承体系例子三&#xff1a;异常的重…

代码随想录算法训练营第二十九天|491.递增子序列、46.全排列、46.全排列II

491. 非递减子序列 思路&#xff1a; 在90.子集II (opens new window)中我们是通过排序&#xff0c;再加一个标记数组来达到去重的目的。 而本题求自增子序列&#xff0c;是不能对原数组进行排序的&#xff0c;排完序的数组都是自增子序列了。 所以不能使用之前的去重逻辑&…

「GO基础」起源与演进

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

Spring容器结构

文章目录 1.基本介绍1.Spring5官网2.API文档3.Spring核心学习内容4.几个重要概念 2.快速入门1.需求分析2.入门案例1.新建Java项目2.导入jar包3.编写Monster.java4.src下编写Spring配置文件1.创建spring配置文件&#xff0c;名字随意&#xff0c;但是需要放在src下2.创建Spring …

Python景区票务人脸识别系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Zabbix监控内容

目录 一、自定义监控内容 1、在客户端创建自定义key 1.1明确需要执行的linux命令 1.2创建zabbix监控项配置文件&#xff0c;用于自定义Key 1.3服务端验证测试 2、在Web界面创建自定义监控模板 2.1创建模板 2.2创建应用集&#xff08;用于管理监控项&#xff09; 2.3创建…

探索音质与价格的平衡点:iBasso DX260播放器体验

专业的音乐播放器作为音乐爱好者们追求高音质体验的重要工具&#xff0c;但是因为市面上这类产品价格差距往往很大&#xff0c;选择也是非常丰富&#xff0c;所以对于新手来说往往会难以抉择。而且今天的音乐播放器在高端和低端市场也是有着云泥之别&#xff0c;想要找一款在价…

PCB---Design Entry cis 绘图 导出

修改纸张大小&#xff1a; 画图前准备&#xff1a;导入 画图&#xff1a; 习惯&#xff1a; 电源朝上 地朝下 配置pbc_footprint编号&#xff1a; 都配置好编号就可以导出了 导出&#xff1a;

SGI_STL空间配置器源码剖析(五)_S_chunk_alloc函数、oom和优点

_S_chunk_alloc函数是操作自由链表分配小内存、内存不够时还会调用开辟内存函数&#xff0c;个人认为是空间配置器源码中最精华的一个函数&#xff0c;其思想真是精辟&#xff01; _S_chunk_alloc代码及解析如下&#xff1a; /* We allocate memory in large chunks in order…

腾讯云优惠券介绍及领取教程详解

腾讯云是腾讯集团倾力打造的云计算品牌&#xff0c;提供全球领先的云计算、大数据、人工智能等技术产品与服务&#xff0c;以卓越的科技能力打造丰富的行业解决方案&#xff0c;构建开放共赢的云端生态&#xff0c;推动产业互联网建设&#xff0c;助力各行各业实现数字化升级。…

深入理解JVM中的G1垃圾收集器原理、过程和参数配置

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;垃圾收集&#xff08;GC&#xff09;是一个自动管理内存的过程&#xff…

信息系统项目管理师0053:设计和实施(4信息系统管理—4.1管理方法—4.1.3设计和实施)

点击查看专栏目录 文章目录 4.1.3设计和实施1.设计方法2.架构模式4.1.3设计和实施 开展信息系统设计和实施,首先需要将业务需求转换为信息系统架构,信息系统架构为将组织业务战略转换为信息系统的计划提供了蓝图。信息系统是支持组织中信息流动和处理的所有基础,包括硬件、软…

消息中间件Kafka分布式数据处理平台

目录 一.Kafka基本介绍 1.定义 2.特点 &#xff08;1&#xff09;高吞吐量、低延迟 &#xff08;2&#xff09;可扩展性 &#xff08;3&#xff09;持久性、可靠性 &#xff08;4&#xff09;容错性 &#xff08;5&#xff09;高并发 3.系统架构 &#xff08;1&#…

向量数据库与图数据库:理解它们的区别

作者&#xff1a;Elastic Platform Team 大数据管理不仅仅是尽可能存储更多的数据。它关乎能够识别有意义的见解、发现隐藏的模式&#xff0c;并做出明智的决策。这种对高级分析的追求一直是数据建模和存储解决方案创新的驱动力&#xff0c;远远超出了传统关系数据库。 这些创…

4核8G配置服务器多少钱?2024年阿里云服务器700元1年价格便宜

4核8G配置服务器多少钱&#xff1f;2024年阿里云服务器700元1年价格便宜。阿里云4核8G服务器租用优惠价格700元1年&#xff0c;配置为ECS通用算力型u1实例&#xff08;ecs.u1-c1m2.xlarge&#xff09;4核8G配置、1M到3M带宽可选、ESSD Entry系统盘20G到40G可选&#xff0c;CPU采…

手机拍照技术

拍照技巧 说明: 本文将主要介绍摄影和手机常见技巧&#xff1b; 1. 摄影的基本知识 **说明&#xff1a;**关于摄影&#xff0c;手机和相机的原理都是相同的&#xff0c;不同的是相机在很多方面优于手机&#xff0c;但是专业的设备对于我们这种的非专业的人来说&#xff0c;刚…

OpenCV从入门到精通实战(二)——文档OCR识别(tesseract)

导入环境 导入必要的库 numpy: 用于处理数值计算。 argparse: 用于处理命令行参数。 cv2: OpenCV库&#xff0c;用于图像处理。 import numpy as np import argparse import cv2设置命令行参数 ap argparse.ArgumentParser() ap.add_argument("-i", "--imag…

如何获取手机root权限?

获取手机的 root 权限通常是指在 Android 设备上获取超级用户权限&#xff0c;这样用户就可以访问和修改系统文件、安装定制的 ROM、管理应用权限等。然而&#xff0c;需要注意的是&#xff0c;获取 root 权限可能会导致手机失去保修、安全性降低以及使系统变得不稳定。在获取 …