
从零构建通讯器--4.2读取配置文件和检查内存泄漏、设置可执行程序的标题(名称)
使用例子讲解: 上面的./nginx HEP SUMMARY显示:9个分配,8个释放,差1就表示没事 LEAK SUMMARY显示:内存泄漏的信息
方法:参考nginx的源码ngx_setproctitle.c实现的,我依照整个创建了 ngx_setproctile.cxx 原理: (1)//printf(“argc=%d,argv[0]=%s\n”,argc,argv[0]); //strcpy(argv[0],“ce”);//ps -elf命令看出,前两个字符变成ce了 //strcpy(argv[0],“c2212212121322324323e”); argv内存之后,接着连续的就是环境变量参数信息内存【是咱们这个可执行程序执行时有关的所有环境变量参数信息】,environ内存和argv内存紧紧的挨着
(2)把环境变量的数据搬家,拷贝到额外分配的内存中去 ngx_setproctile.cxx
效果:
发布日期:2021-05-04 18:23:24
浏览次数:26
分类:精选文章
本文共 12468 字,大约阅读时间需要 41 分钟。
1.基础设施只配置文件读取
(1.1)前提内容和修改
(1)配置文件:文本文件,除了注释行不要有中文 (2)以#开头的行作为配置行 (3)我们这个框架(项目),第一个要解决的问题是读取配置文件中的配置项(读到内存中来)(1.2)配置文件读取功能实战代码
写代码考虑别人感受,让别人更容易读懂和理解 #是注释行, #每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符; #[开头的表示组信息,也等价于注释行 这是配置文件的内容,配置文件名字是 nginx.conf(1.2.1)要读的配置文件nginx.conf
#是注释行,#每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符;#[开头的表示组信息,也等价于注释行[Socket]ListenPort = 5678 DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g
(1.2.2)读配置文件的代码ngx_c_conf.cxx,有c表示有class存在
//系统头文件放上边#include#include #include #include //自定义头文件放下边,因为g++中用了-I参数,所以这里用<>也可以#include "ngx_func.h" //函数声明#include "ngx_c_conf.h" //和配置文件处理相关的类,名字带c_表示和类有关//静态成员赋值CConfig *CConfig::m_instance = NULL;//构造函数CConfig::CConfig(){ //用不到保持为空}//析构函数CConfig::~CConfig(){ //在析构函数中释放 std::vector ::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)) //检查文件是否结束 ,没有结束则条件成立,不断循环,fp为读取指针 { //注意写法的严密性,商业代码,就是要首先确保代码的严密性 if(fgets(linebuf,500,fp) == NULL) //从文件中读数据,每次读一行,一行最多不要超过500个字符 continue;//读失败直接continue if(linebuf[0] == 0)//读到空行,continue continue; //处理注释行,读到换行、#、分号直接continue if(*linebuf==';' || *linebuf==' ' || *linebuf=='#' || *linebuf=='\t'|| *linebuf=='\n') continue; lblprocstring: //屁股后边若有换行,回车,空格等都截取掉 if(strlen(linebuf) > 0)//大于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,'=');//ptmp指向等号= if(ptmp != NULL) { //定义结构体指针=new 结构 LPCConfItem p_confitem = new CConfItem; //注意前边类型带LP,后边new这里的类型不带 memset(p_confitem,0,sizeof(CConfItem)); strncpy(p_confitem->ItemName,linebuf,(int)(ptmp-linebuf)); //等号之前的(也就是左侧,ListenPort)拷贝到p_confitem->ItemName,能存50个字符 strcpy(p_confitem->ItemContent,ptmp+1); //ptmp+1表示指向5678,也就是等号右侧的拷贝到p_confitem->ItemContent Rtrim(p_confitem->ItemName);//把ListenPort右边的空格截掉 Ltrim(p_confitem->ItemName);//左边的空格截掉,函数是自己包装的,放在ngx_string.cxx里面 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 ::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 ::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;//没找到会返回缺省值}
(1.2.3)ngx_string.cxx
处理字符串的具体函数#include#include //一些和字符串处理相关的函数,准备放这里//截取字符串尾部空格void Rtrim(char *string) { size_t len = 0; if(string == NULL) return; len = strlen(string); while(len > 0 && string[len-1] == ' ') //位置换一下 string[--len] = 0; return; }//截取字符串首部空格void Ltrim(char *string){ size_t len = 0; len = strlen(string); char *p_tmp = string; if( (*p_tmp) != ' ') //不是以空格开头,不用截断 return; //找第一个不为空格的 while((*p_tmp) != '\0') { if( (*p_tmp) == ' ') p_tmp++; else break; } if((*p_tmp) == '\0') //全是空格 { *string = '\0'; return; } char *p_tmp2 = string; while((*p_tmp) != '\0') { (*p_tmp2) = (*p_tmp); p_tmp++; p_tmp2++; } (*p_tmp2) = '\0'; return;}
(1.2.4)涉及到的头文件
1.ngx_global.h#ifndef __NGX_GBLDEF_H__#define __NGX_GBLDEF_H__//一些比较通用的定义放在这里//结构定义typedef struct{ char ItemName[50];//定义配置文件名字例如,ListenPort char ItemContent[500];//里面是内容,如5678}CConfItem,*LPCConfItem;//外部全局量声明extern char **g_os_argv;extern char *gp_envmem; extern int g_environlen; #endif
2.ngx_c_conf.h
设置装载配置文件细节的头文件#ifndef __NGX_CONF_H__#define __NGX_CONF_H__#include#include "ngx_global.h" //一些全局/通用定义//类名可以遵照一定的命名规则规范,比如老师这里,第一个字母是C,后续的单词首字母大写class CConfig //用来读配置{ //---------------------------------------------------//这段代码老师在《c++从入门到精通》 多线程这章老师提过 单例设计模式,就是如下这些代码,大家即便没学过,也可以现在学private: CConfig();public: ~CConfig();private: static CConfig *m_instance;public: static CConfig* GetInstance() //GetInstance可以直接调用 { if(m_instance == NULL) { //锁 if(m_instance == NULL)//第一次调用为空,第一次调用在主线程中调用,确保能够安全的new出来 { 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 m_ConfigItemList; //存储配置信息的列表};#endif
3.ngx_func.h
定义字符串裁剪空格的相关函数#ifndef __NGX_FUNC_H__#define __NGX_FUNC_H__//函数声明-------------------------------------------//字符串相关函数void Rtrim(char *string);void Ltrim(char *string);//设置可执行程序标题相关函数void ngx_init_setproctitle();void ngx_setproctitle(const char *title);#endif
2.查内存泄漏
大概:
检查泄漏的工具: Valgrind: ①帮助程序员寻找程序里的bug和改进程序性能的工具集。擅长是发现内存的管理问题; ②里边有若干工具,其中最重要的是Memcheck(内存检查)工具,用于检查内存的泄漏;sudo apt-get install valgrind #安装Valgrindvalgrind -h #看帮助valgrind --version #看版本
(2.1)memcheck的基本功能,能发现如下的问题;
a)使用未初始化的内存 b)使用已经释放了的内存 c)使用超过malloc()分配的内存 d)对堆栈的非法访问 e)申请的内存是否有释放(重点) f)malloc/free,new/delete申请和释放内存的匹配 g)memcpy()内存拷贝函数中源指针和目标指针重叠; (2.2)内存泄漏检查示范 格式: 1)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 ./nginx
最终用的命令: 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
完整命令的信息:
valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx
3.设置可执行程序的标题(名称)
作用:区分主进程和子进程名字(一个master和多个worker进程)
效果:图示如下


#include#include #include //env#include #include "ngx_global.h"//设置可执行程序标题相关函数:分配内存,并且把环境变量拷贝到新内存中来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;}//设置可执行程序标题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;}
nginx.cxx
#include#include #include #include #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); } /*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("--------------------------------------------------------"); */ /* 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_setproctitle("nginx: master process"); //printf("argc=%d,argv[0]=%s\n",argc,argv[0]); //strcpy(argv[0],"ce");//ps -elf命令看出,前两个字符变成ce了 //strcpy(argv[0],"c2212212121322324323e"); //printf("非常高兴,大家和老师一起学习《linux c++通讯架构实战》\n"); //printf("evriron[0]=%s\n" , environ[0]); //printf("evriron[1]=%s\n" , environ[1]); //printf("evriron[2]=%s\n" , environ[2]); //printf("evriron[3]=%s\n" , environ[3]); //printf("evriron[4]=%s\n" , environ[4]); //for(int i = 0; i < argc; ++i) //{ // printf("argv[%d]地址=%x " ,i,(unsigned int)((unsigned long)argv[i])); // 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]); //} //获取配置文件信息的用法 //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); //} //myconf(); //mysignal(); for(;;) { sleep(1); //休息1秒 printf("休息1秒\n"); } //对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放 if(gp_envmem)//释放为了设置标题分配的内存 { delete []gp_envmem; gp_envmem = NULL; } printf("程序退出,再见!\n"); return 0;}
注意


发表评论
最新留言
逛到本站,mark一下
[***.202.152.39]2025年04月07日 04时06分30秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
2020.2.13普及C组 晾衣绳【纪中】【排序】
2019-03-04
2020.2.13普及C组 罗密欧与朱丽叶的约会【纪中】【前缀和】
2019-03-04
2020.2.29普及C组 子矩阵(submatrix)【纪中】【DP】【DFS】
2019-03-04
纪中2020.3.4普及C组模拟赛总结
2019-03-04
2020.3.8普及C组 自动匹配(auto)【纪中】【二分】
2019-03-04
洛谷 P3374 【模板】树状数组 1
2019-03-04
纪中2020.3.18普及C组模拟赛总结
2019-03-04
纪中2020.4.8普及C组模拟赛总结
2019-03-04
YbtOJ 递推算法课堂过关 例5 平铺方案【递推(简单DP)】
2019-03-04
YbtOJ hash和hash表课堂过关 例1 字符串哈希【hash】
2019-03-04
数论大杂烩——From GDKOI2021 【数论基础】【学习笔记】
2019-03-04
YbtOJ hash和hash表课堂过关 例4 单词背诵【hash】【二分】
2019-03-04
【笔记】Linux 基本操作
2019-03-04
CSUST 2021 周赛 2 题解
2019-03-04
我们真的需要模型压缩吗
2019-03-04
【人脸识别】基于matlab GUI灰度化教室人数统计【含Matlab源码 602期】
2019-03-04
前后端数据交互之表单
2019-03-04
剑指offer JZ12 数值的整数次方
2019-03-04
剑指offer JZ15 反转链表
2019-03-04