赞
踩
一个项目要启动,需要配置很多信息,第一项就是学习如何配置一个项目
- #是注释行,
- #每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符;
-
-
- #[开头的表示组信息,也等价于注释行
- [Socket]
- ListenPort = 5678
-
- DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g
我们的目的是:
1.将 ListenPort的值取出来
2.将DBInfo的值取出来
-
- #ifndef __NGX_CONF_H__
- #define __NGX_CONF_H__
-
- #include <vector>
-
- #include "ngx_global.h" //一些全局/通用定义
-
- //类名可以遵照一定的命名规则规范,比如老师这里,第一个字母是C,后续的单词首字母大写
- class CConfig
- {
- //---------------------------------------------------
- //这段代码老师在《c++从入门到精通》 多线程这章老师提过 单例设计模式,就是如下这些代码,大家即便没学过,也可以现在学
- private:
- CConfig();
- public:
- ~CConfig();
- private:
- static CConfig *m_instance;
-
- public:
- static CConfig* GetInstance()
- {
- if(m_instance == NULL)
- {
- //锁
- if(m_instance == NULL)
- {
- m_instance = new CConfig();
- static CGarhuishou cl;
- }
- //放锁
- }
- return m_instance;
- }
- class CGarhuishou //类中套类,用于释放对象
- {
- public:
- ~CGarhuishou()
- {
- if (CConfig::m_instance)
- {
- delete CConfig::m_instance;
- CConfig::m_instance = NULL;
- }
- }
- };
- //---------------------------------------------------
- public:
- bool Load(const char *pconfName); //装载配置文件
- const char *GetString(const char *p_itemname);
- int GetIntDefault(const char *p_itemname,const int def);
-
- public:
- std::vector<LPCConfItem> m_ConfigItemList; //存储配置信息的列表
-
- };
-
- #endif
-
- //系统头文件放上边
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <vector>
-
- //自定义头文件放下边,因为g++中用了-I参数,所以这里用<>也可以
- #include "ngx_func.h" //函数声明
- #include "ngx_c_conf.h" //和配置文件处理相关的类,名字带c_表示和类有关
-
- //静态成员赋值
- CConfig *CConfig::m_instance = NULL;
-
- //构造函数
- CConfig::CConfig()
- {
- }
-
- //析构函数
- CConfig::~CConfig()
- {
- std::vector<LPCConfItem>::iterator pos;
- for(pos = m_ConfigItemList.begin(); pos != m_ConfigItemList.end(); ++pos)
- {
- delete (*pos);
- }//end for
- m_ConfigItemList.clear();
- }
-
- //装载配置文件
- bool CConfig::Load(const char *pconfName)
- {
- FILE *fp;
- fp = fopen(pconfName,"r");
- if(fp == NULL)
- return false;
-
- //每一行配置文件读出来都放这里
- char linebuf[501]; //每行配置都不要太长,保持<500字符内,防止出现问题
-
- //走到这里,文件打开成功
- while(!feof(fp)) //检查文件是否结束 ,没有结束则条件成立
- {
- //大家要注意老师的写法,注意写法的严密性,商业代码,就是要首先确保代码的严密性
- if(fgets(linebuf,500,fp) == NULL) //从文件中读数据,每次读一行,一行最多不要超过500个字符
- continue;
-
- if(linebuf[0] == 0)
- continue;
-
- //处理注释行
- if(*linebuf==';' || *linebuf==' ' || *linebuf=='#' || *linebuf=='\t'|| *linebuf=='\n')
- continue;
-
- lblprocstring:
- //屁股后边若有换行,回车,空格等都截取掉
- if(strlen(linebuf) > 0)
- {
- //10 换行,13回车,32 是空格
- if(linebuf[strlen(linebuf)-1] == 10 || linebuf[strlen(linebuf)-1] == 13 || linebuf[strlen(linebuf)-1] == 32)
- {
- linebuf[strlen(linebuf)-1] = 0;
- goto lblprocstring;
- }
- }
- if(linebuf[0] == 0)
- continue;
- if(*linebuf=='[') //[开头的也不处理
- continue;
-
- //这种 “ListenPort = 5678”走下来;
- char *ptmp = strchr(linebuf,'=');
- if(ptmp != NULL)
- {
- LPCConfItem p_confitem = new CConfItem; //注意前边类型带LP,后边new这里的类型不带
- memset(p_confitem,0,sizeof(CConfItem));
- strncpy(p_confitem->ItemName,linebuf,(int)(ptmp-linebuf)); //等号左侧的拷贝到p_confitem->ItemName
- strcpy(p_confitem->ItemContent,ptmp+1); //等号右侧的拷贝到p_confitem->ItemContent
-
- Rtrim(p_confitem->ItemName);
- Ltrim(p_confitem->ItemName);
- Rtrim(p_confitem->ItemContent);
- Ltrim(p_confitem->ItemContent);
-
- //printf("itemname=%s | itemcontent=%s\n",p_confitem->ItemName,p_confitem->ItemContent);
- m_ConfigItemList.push_back(p_confitem); //内存要释放,因为这里是new出来的
- } //end if
- } //end while(!feof(fp))
-
- fclose(fp); //这步不可忘记
- return true;
- }
-
- //根据ItemName获取配置信息字符串,不修改不用互斥
- const char *CConfig::GetString(const char *p_itemname)
- {
- std::vector<LPCConfItem>::iterator pos;
- for(pos = m_ConfigItemList.begin(); pos != m_ConfigItemList.end(); ++pos)
- {
- if(strcasecmp( (*pos)->ItemName,p_itemname) == 0)
- return (*pos)->ItemContent;
- }//end for
- return NULL;
- }
- //根据ItemName获取数字类型配置信息,不修改不用互斥
- int CConfig::GetIntDefault(const char *p_itemname,const int def)
- {
- std::vector<LPCConfItem>::iterator pos;
- for(pos = m_ConfigItemList.begin(); pos !=m_ConfigItemList.end(); ++pos)
- {
- if(strcasecmp( (*pos)->ItemName,p_itemname) == 0)
- return atoi((*pos)->ItemContent);
- }//end for
- return def;
- }
-
-
-
-
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <string.h>
-
- #include "ngx_c_conf.h" //和配置文件处理相关的类,名字带c_表示和类有关
- #include "ngx_signal.h"
- #include "ngx_func.h" //各种函数声明
-
- //和设置标题有关的全局量
- char **g_os_argv; //原始命令行参数数组,在main中会被赋值
- char *gp_envmem = NULL; //指向自己分配的env环境变量的内存
- int g_environlen = 0; //环境变量所占内存大小
-
- int main(int argc, char *const *argv)
- {
- g_os_argv = (char **) argv;
- ngx_init_setproctitle(); //把环境变量搬家
-
- //我们在main中,先把配置读出来,供后续使用
- CConfig *p_config = CConfig::GetInstance(); //单例类
- if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存
- {
- printf("配置文件载入失败,退出!\n");
- exit(1);
- }
-
-
- //获取配置文件信息的用法
- int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值
- printf("port=%d\n",port);
- const char *pDBInfo = p_config->GetString("DBInfo");
- if(pDBInfo != NULL)
- {
- printf("DBInfo=%s\n",pDBInfo);
- }
-
- for(;;)
- {
- sleep(1); //休息1秒
- printf("休息1秒\n");
- }
-
-
- printf("程序退出,再见!\n");
- return 0;
- }
-
-
- hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ ./nginx
- port=5678
- DBInfo=127.0.0.1;1234;myr;123456;mxdb_g
- 休息1秒
- 休息1秒
sudo apt-get install valgrind
valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx
//Valgrind:帮助程序员寻找程序里的bug和改进程序性能的工具集。擅长是发现内存的管理问题;
//里边有若干工具,其中最重要的是Memcheck(内存检查)工具,用于检查内存的泄漏;
//(2.1)memcheck的基本功能,能发现如下的问题;
//a)使用未初始化的内存
//b)使用已经释放了的内存
//c)使用超过malloc()分配的内存
//d)对堆栈的非法访问
//e)申请的内存是否有释放*****
//f)malloc/free,new/delete申请和释放内存的匹配
//g)memcpy()内存拷贝函数中源指针和目标指针重叠;
//(2.2)内存泄漏检查示范
//所有应该释放的内存,都要释放掉,作为服务器程序开发者,要绝对的严谨和认真
//格式:
//valgrind --tool=memcheck 一些开关 可执行文件名
//--tool=memcheck :使用valgrind工具集中的memcheck工具
//--leak-check=full : 指的是完全full检查内存泄漏
//--show-reachable=yes :是显示内存泄漏的地点
//--trace-children = yes :是否跟入子进程
//--log-file=log.txt:讲调试信息输出到log.txt,不输出到屏幕
//最终用的命令:
//valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx
//查看内存泄漏的三个地方:
//(1) 9 allocs, 8 frees 差值是1,就没泄漏,超过1就有泄漏
//(2)中间诸如: by 0x401363: CConfig::Load(char const*) (ngx_c_conf.cxx:77)和我们自己的源代码有关的提示,就要注意;
//(3)LEAK SUMMARY:definitely lost: 1,100 bytes in 2 blocks
注意这里:==3236== total heap usage: 10 allocs, 9 frees, 79,021 bytes allocated
- hunandede@hunandede-virtual-machine:/mnt/hgfs/linux$ cd 4-2/nginx/
- hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx
- ==3236== Memcheck, a memory error detector
- ==3236== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
- ==3236== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
- ==3236== Command: ./nginx
- ==3236==
- port=5678
- DBInfo=127.0.0.1;1234;myr;123456;mxdb_g
- 程序退出,再见!
- ==3236==
- ==3236== HEAP SUMMARY:
- ==3236== in use at exit: 72,704 bytes in 1 blocks
- ==3236== total heap usage: 10 allocs, 9 frees, 79,021 bytes allocated
- ==3236==
- ==3236== 72,704 bytes in 1 blocks are still reachable in loss record 1 of 1
- ==3236== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
- ==3236== by 0x4EC3EFF: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
- ==3236== by 0x40106F9: call_init.part.0 (dl-init.c:72)
- ==3236== by 0x401080A: call_init (dl-init.c:30)
- ==3236== by 0x401080A: _dl_init (dl-init.c:120)
- ==3236== by 0x4000C69: ??? (in /lib/x86_64-linux-gnu/ld-2.23.so)
- ==3236==
- ==3236== LEAK SUMMARY:
- ==3236== definitely lost: 0 bytes in 0 blocks
- ==3236== indirectly lost: 0 bytes in 0 blocks
- ==3236== possibly lost: 0 bytes in 0 blocks
- ==3236== still reachable: 72,704 bytes in 1 blocks
- ==3236== suppressed: 0 bytes in 0 blocks
- ==3236==
- ==3236== For counts of detected and suppressed errors, rerun with: -v
- ==3236== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
- hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$
我们这样执行写好的代码,就是说带了参数 aa bb cc
./nginx aa bb cc
- hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ ./nginx aa bb cc
- port=5678
- DBInfo=127.0.0.1;1234;myr;123456;mxdb_g
- 休息1秒
- 休息1秒
- 休息1秒
- 休息1秒
然后使用 如下命令查看
ps -eo pid,ppid,sid,tty,pgrp,comm,stat,cmd | grep -E 'bash|PID|nginx'
- hunandede@hunandede-virtual-machine:~$ ps -eo pid,ppid,sid,tty,pgrp,comm,stat,cmd | grep -E 'bash|PID|nginx'
- PID PPID SID TT PGRP COMMAND STAT CMD
- 2652 2651 2652 pts/9 2652 bash Ss -bash
- 3326 3325 3326 pts/10 3326 bash Ss -bash
- 3348 2652 2652 pts/9 3348 nginx S+ ./nginx aa bb cc
- 3371 3326 3326 pts/10 3370 grep R+ grep --color=auto -E bash|PID|nginx
- hunandede@hunandede-virtual-machine:~$
注意下图红色的部分,实际上我们执行的该可执行程序的 加上参数
在ps中最终会显示在这里。
我们这里的想法就是改动这块。
//argc:命令行参数的个数
//argv:是个数组,每个数组元素都是指向一个字符串的char *,里边存储的内容是所有命令行参数;
例如我们在执行nginx的时候,加上了3个参数
./nginx -v -s 5
那么对应的argc 和 argv数组的每个值如下:
//argc = 4
//argv[0] = ./nginx ----指向的就是可执行程序名: ./nginx
//argv[1] = -v
//argv[2] = -s
//argv[3] = 5
//argv内存之后,接着连续的就是环境变量参数信息内存【是咱们这个可执行程序执行时有关的所有环境变量参数信息】
//可以通过一个全局的environ[char **]就可以访问
//environ内存和argv内存紧紧的挨着
我们先来验证自己的猜测是否正确 。
- int main(int argc, char *const *argv)
- {
- cout << "验证argc 和 argv 以及 环境变量的存储是否是我们理解的布局 start" << endl;
-
- cout << "参数个数argc的值为:" << argc << endl;
- for (int i = 0; i < argc; ++i)
- {
- //这里注意老师的写法,在c中打印argv ,也就是 char *argv[]中的地址的写法,argv是一个指针数组,本质上是一个数组,这个数组的每一项都是一个指针
- // cout << "myargv[" << i << "] 的地址为:" << *(&argv[i]) << endl;//myargv[0] 的地址为:./nginx
- // cout << "myargv[" << i << "] 的1地址为:" << argv[i] << endl;//myargv[0] 的1地址为:./nginx
- // cout << "myargv[" << i << "] 的2地址为:" << *argv[i] << endl;myargv[0] 的2地址为:.
- printf("argv[%d]地址=%x ", i, (unsigned int)((unsigned long)argv[i]));//由于数组中的每一项都是指针,我们要先拿到数组里面的 指针,由于指针和long 在32位上占用 4个字节,在64位上占用8个字节,因此可以先将 指针转成 unsigned long,由于打印的时候需要的%x,因此在转成(unsigned int)
- //这里可以思考一个额外的问题,这里将指针 从 unsigned long 强行转成 unsigned int 会不会有丢失呢?
-
- printf("argv[%d]内容=%s\n", i, argv[i]);
- }
- // 下边环境变量随便打两个,只要打印的地址和上面的地址能够连贯,就说明我们关于 参数的地址和环境变量的地址挨着的判断是正确的
- for (int i = 0; i < 2; ++i)
- {
- printf("evriron[%d]地址=%x ", i, (unsigned int)((unsigned long)environ[i]));
- printf("evriron[%d]内容=%s\n", i, environ[i]);
- }
- cout << "验证argc 和 argv 以及 环境变量的存储是否是我们理解的布局 end" << endl;
- //这里注意的是,这段代码在vs2017上和 在linux上执行的结果不同,在linux的内存图确实如我们所想,但是在vs2017上,是environ的内存在前面,argv的内存在后边
- }
//这里注意的是,这段代码在vs2017上和 在linux上执行的结果不同,在linux的内存图确实如我们所想,但是在vs2017上,是environ的内存在前面,argv的内存在后边
注意这两行
argv[3]地址=93a03693 argv[3]内容=cc
evriron[0]地址=93a03696 evriron[0]内容=LC_PAPER=zh_CN.UTF-8
evrion[0]的地址刚好紧挨着 argv[3],这说明我们的猜想是对的
- hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ ./nginx aa bb cc
- 验证argc 和 argv 以及 环境变量的存储是否是我们理解的布局 start
- 参数个数argc的值为:4
- argv[0]地址=93a03685 argv[0]内容=./nginx
- argv[1]地址=93a0368d argv[1]内容=aa
- argv[2]地址=93a03690 argv[2]内容=bb
- argv[3]地址=93a03693 argv[3]内容=cc
- evriron[0]地址=93a03696 evriron[0]内容=LC_PAPER=zh_CN.UTF-8
- evriron[1]地址=93a036ab evriron[1]内容=LC_ADDRESS=zh_CN.UTF-8
- 验证argc 和 argv 以及 环境变量的存储是否是我们理解的布局 end
那么在linux上的原理图大致如下:
- //第一步 环境变量原先的地址
- cout<<"环境变量原先的地址"<<endl;
- for (int i = 0; environ[i]; i++)
- {
- printf("evriron[%d]地址=%x " ,i,(unsigned int)((unsigned long)environ[i]));
- printf("evriron[%d]内容=%s\n" ,i,environ[i]);
- }
- printf("--------------------------搬家中------------------------------\n");
-
- //让环境变量搬家
- g_os_argv = (char **)argv;
- ngx_init_setproctitle(); // 把环境变量搬家
-
- cout<<"环境变量搬家后的地址"<<endl;
- for (int i = 0; environ[i]; i++)
- {
- printf("evriron[%d]地址=%x " ,i,(unsigned int)((unsigned long)environ[i]));
- printf("evriron[%d]内容=%s\n" ,i,environ[i]);
- }
其核心是这个方法
ngx_init_setproctitle(); // 把环境变量搬家
- //设置可执行程序标题相关函数:分配内存,并且把环境变量拷贝到新内存中来
- void ngx_init_setproctitle()
- {
- int i;
- //统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记
- for (i = 0; environ[i]; i++)
- {
- g_environlen += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来
- } //end for
-
- //这里无需判断penvmen == NULL,有些编译器new会返回NULL,有些会报异常,但不管怎样,如果在重要的地方new失败了,你无法收场,让程序失控崩溃,助你发现问题为好;
- gp_envmem = new char[g_environlen];
- memset(gp_envmem,0,g_environlen); //内存要清空防止出现问题
-
- char *ptmp = gp_envmem;
-
- //把原来的内存内容搬到新地方来
- for (i = 0; environ[i]; i++)
- {
- size_t size = strlen(environ[i])+1 ; //不要拉下+1,否则内存全乱套了,因为strlen是不包括字符串末尾的\0的
- strcpy(ptmp,environ[i]); //把原环境变量内容拷贝到新地方【新内存】
- environ[i] = ptmp; //然后还要让新环境变量指向这段新内存
- ptmp += size;
- }
- return;
- }
// 第二步,设置标题代码title,我要保证所有命令行参数我都不 用了,如果还要用,那么建议存储起来,并告知其他模块的owner,如果要使用启动程序时的参数,请到存储的地方去拿。这个要其他用到的owner sync到位
ngx_setproctitle("nginx: my master process");
- //设置可执行程序标题
- void ngx_setproctitle(const char *title)
- {
- //我们假设,所有的命令 行参数我们都不需要用到了,可以被随意覆盖了;
- //注意:我们的标题长度,不会长到原始标题和原始环境变量都装不下,否则怕出问题,不处理
-
- //(1)计算新标题长度
- size_t ititlelen = strlen(title);
-
- //(2)计算总的原始的argv那块内存的总长度【包括各种参数】
- size_t e_environlen = 0; //e表示局部变量
- for (int i = 0; g_os_argv[i]; i++)
- {
- e_environlen += strlen(g_os_argv[i]) + 1;
- }
-
- size_t esy = e_environlen + g_environlen; //argv和environ内存总和
- if( esy <= ititlelen)
- {
- //你标题多长啊,我argv和environ总和都存不下?注意字符串末尾多了个 \0,所以这块判断是 <=【也就是=都算存不下】
- return;
- }
-
- //空间够保存标题的,够长,存得下,继续走下来
-
- //(3)设置后续的命令行参数为空,表示只有argv[]中只有一个元素了,这是好习惯;防止后续argv被滥用,因为很多判断是用argv[] == NULL来做结束标记判断的;
- g_os_argv[1] = NULL;
-
- //(4)把标题弄进来,注意原来的命令行参数都会被覆盖掉,不要再使用这些命令行参数,而且g_os_argv[1]已经被设置为NULL了
- char *ptmp = g_os_argv[0]; //让ptmp指向g_os_argv所指向的内存
- strcpy(ptmp,title);
- ptmp += ititlelen; //跳过标题
-
- //(5)把剩余的原argv以及environ所占的内存全部清0,否则会出现在ps的cmd列可能还会残余一些没有被覆盖的内容;
- size_t cha = esy - ititlelen; //内存总和减去标题字符串长度(不含字符串末尾的\0),剩余的大小,就是要memset的;
- memset(ptmp,0,cha);
- return;
- }
在一个终端让其跑起来
hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ ./nginx aa bb cc
在另一个终端测试看是否改动成功,我们在代码中是写成替换成"my master process"
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。