区块链技术博客
www.b2bchain.cn

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

这篇文章主要介绍了(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战的讲解,通过具体代码实例进行24116 讲解,并且分析了(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战的详细步骤与相关技巧,需要的朋友可以参考下https://www.b2bchain.cn/?p=24116

本文实例讲述了2、树莓派设置连接WiFi,开启VNC等等的讲解。分享给大家供大家参考文章查询地址https://www.b2bchain.cn/7039.html。具体如下:

目录

基础设施之配置文件读取

配置文件读取实战代码

内存泄漏的检查工具

内存泄漏检查示范

设置可执行程序的标题(名称)

设置可执行程序的标题实战代码

基础设施之配置文件读取

  • 使用配置文件,使我们的服务器程序有了极大的灵活性(比如修改端口号),是我们作为服务器程序开发者,必须要首先搞定的问题。
  • 配置文件:文本文件,里边除了注释行之外不要用中文,只在配置文件中使用字母,数字下划线。
    • 以#号开头的行作为注释行(注释行可以有中文)。
    • 我们这个框架(项目),第一个要解决的问题是读取配置文件中的配置项(读到内存中来)。

【配置文件读取功能实战代码】

  • 写代码要多顾及别人感受,让别人更容易读懂和理解,不要刻意去炫技。
  • 该缩进的必须要缩进,该对齐的要对齐,该注释的要注释,这些切记。

配置文件读取实战代码

【nginx.conf】

  • 在项目根目录下,创建nginx.conf配置文件
#是注释行, #每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符;    #[开头的表示组信息,也等价于注释行 [Socket] ListenPort = 5678      DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g

【_include/nginx_global.h】

#ifndef __NGX_GBLDEF_H__ #define __NGX_GBLDEF_H__  //一些比较通用的定义放在这里   //结构定义 typedef struct { 	char ItemName[50];     //配置文件 等号左边的字符 	char ItemContent[500]; //配置文件 等号右边的字符 }CConfItem,*LPCConfItem;  //外部全局量声明 extern char  **g_os_argv; extern char  *gp_envmem;  extern int   g_environlen;   #endif

【_include/nginx_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  

【app/nginx_string.cxx】

#include <stdio.h> #include <string.h>  //一些和字符串处理相关的函数,准备放这里  //截取字符串尾部空格 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) != '') 	{ 		if( (*p_tmp) == ' ') 			p_tmp++; 		else 			break; 	} 	if((*p_tmp) == '') //全是空格 	{ 		*string = ''; 		return; 	} 	char *p_tmp2 = string;  	while((*p_tmp) != '') 	{ 		(*p_tmp2) = (*p_tmp); 		p_tmp++; 		p_tmp2++; 	} 	(*p_tmp2) = '';     return; }

【_include/nginx_c_conf.h】

#ifndef __NGX_CONF_H__ #define __NGX_CONF_H__  #include <vector>  #include "ngx_global.h"  //一些全局/通用定义  //类名可以遵照一定的命名规则规范,比如老师这里,第一个字母是C,后续的单词首字母大写 class CConfig { //--------------------------------------------------- //单例设计模式 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;   //销毁cl的同时,调用其析构函数,借此销毁m_instance  			} 			//放锁		 		} 		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

【app/nginx_c_conf.cxx】

 //系统头文件放上边 #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) 		{ 			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=%sn",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; }

【app/nginx_c_conf.cxx】

//获取配置文件信息的用法     int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值 printf("port=%dn",port); const char *pDBInfo = p_config->GetString("DBInfo"); if(pDBInfo != NULL) {    printf("DBInfo=%sn",pDBInfo); } 

内存泄漏的检查工具

  • Valgrind:帮助程序员寻找程序里的bug和改进程序性能的工具集。擅长是发现内存的管理问题。
    • 里边有若干工具,其中最重要的是Memcheck(内存检查)工具,用于检查内存的泄漏
  • memcheck的基本功能,能发现如下的问题:
    • 使用未初始化的内存
    • 使用已经释放了的内存
    • 使用超过malloc()分配的内存
    • 对堆栈的非法访问
    • 申请的内存是否有释放*****
    • malloc/free,new/delete申请和释放内存的匹配
    • memcpy()内存拷贝函数中源指针和目标指针重叠

【安装valgrind】

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 查看版本及帮助

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

内存泄漏检查示范

  • config.mk设置(调试时设置为true,发布时设置为false)

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

【内存泄漏检查应用案例】

  • 所有应该释放的内存,都要释放掉,作为服务器程序开发者,要绝对的严谨和认真
  • valgrind使用格式
valgrind --tool=memcheck  一些开关  可执行文件名
  • valgrind使用选项
--tool=memcheck :使用valgrind工具集中的memcheck工具 --leak-check=full : 指的是完全full检查内存泄漏 --show-reachable=yes :是显示内存泄漏的地点 --trace-children = yes :是否跟入子进程 --log-file=log.txt:讲调试信息输出到log.txt,不输出到屏幕
  • 使用 valgrind –tool=memcheck  ./nginx 

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

【实验1】

  • 注释掉nginx_c_conf.cxx中的CConfig类的析构函数

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 使用memcheck检测

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 使用 valgrind –tool=memcheck –leak-check=full –show-reachable=yes ./nginx   定位具体泄漏位置

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 找到对应的代码文件

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 查看内存泄漏的三个地方:
    • (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 。(不为0就要注意)

【实验2】

  • 注释掉nginx_c_conf.cxx中的fclose函数

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

使用 valgrind –tool=memcheck –leak-check=full –show-reachable=yes ./nginx   定位具体泄漏位置

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 由输出信息可知,main函数调用lLoad函数,在35行发生内存泄漏

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

设置可执行程序的标题(名称)

  • 如nginx一般是一个master进程4个worker进程,其进程名不同。
  • 注意到main函数:int main(int argc, char *const *argv)
    • argc:命令行参数的个数
    • argv:是个数组,每个数组元素都是指向一个字符串的char *,里边存储的内容是所有命令行参数;

【示例】

./nginx -v -s 5 argc = 4 argv[0] = ./nginx    ----指向的就是可执行程序名: ./nginx argv[1] = -v argv[2] = -s argv[3] = 5 
  • 执行时带参数

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 查看进程信息

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 如何将进程信息中的 ./nginx  改掉?
  • 注意到./nginx 保存到 argv[0] 中,只要 argv[0]的值发生改变,进程名就发生改变
  • 比如你输入 ./nginx -12 -v 568 -q -q

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

【实验1】

  • 在主函数中,改变argv[0]的值

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 测试

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 查看程序信息

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

【实验2】

  • 在主函数中,改变argv[0]的值

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 测试

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 查看程序信息

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

【argv 与环境变量内存相连】

  • argv内存之后,接着连续的就是环境变量参数信息内存(是咱们这个可执行程序执行时有关的所有环境变量参数信息),可以通过一个全局的environ[char **]就可以访问

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 打印环境变量信息

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 执行显示信息如下

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

【验证argv 与环境变量内存相连】

  • 在main函数中添加
for(int i = 0; i < argc; ++i) {             printf("argv[%d]地址=%x    " ,i,(unsigned int)((unsigned long)argv[i]));     printf("argv[%d]内容=%sn",i,argv[i]); } 下边环境变量随便打两个 for(int i = 0; i < 2; ++i) {     printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));     printf("evriron[%d]内容=%sn" ,i,environ[i]); } 
  • 执行结果,字符串gess占5个字节,可知environ内存和argv内存紧紧的挨着

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 改标题如果太长(长度超过原理的参数的字符长度总和),容易把环境变量内存覆盖
  • 修改可执行程序的实现思路:
    • 重新分配一块内存,用来保存environ中的内容
    • 修改argv[0]所指向的内存

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

【代码测试】

  • 移动环境变量代码
#include <unistd.h>  //env #include <string.h>  #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是因为末尾有,是占实际内存位置的,要算进来     } //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是不包括字符串末尾的的         strcpy(ptmp,environ[i]);      //把原环境变量内容拷贝到新地方【新内存】         environ[i] = ptmp;            //然后还要让新环境变量指向这段新内存         ptmp += size;     }     return; }
  • 在main函数中
//...其他代码...  //和设置标题有关的全局量 char **g_os_argv;            //原始命令行参数数组,在main中会被赋值 char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存 int  g_environlen = 0;       //环境变量所占内存大小  int main(int argc, char *const *argv) {      for (int i = 0; environ[i]; i++)     {         printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));         printf("evriron[%d]内容=%sn" ,i,environ[i]);     }     printf("--------------------------------------------------------");          ngx_init_setproctitle();    //把环境变量搬家      for (int i = 0; environ[i]; i++)     {         printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));         printf("evriron[%d]内容=%sn" ,i,environ[i]);     } //... }
  • 由于 gp_envmem = new char[g_environlen];  申请内存没释放,测试出现内存泄漏

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 内存泄漏位置

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 添加内存释放代码

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

设置可执行程序的标题实战代码

【app/ngx_setproctitle.cxx】

 #include <stdio.h> #include <stdlib.h> #include <unistd.h>  //env #include <string.h>  #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是因为末尾有,是占实际内存位置的,要算进来     } //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是不包括字符串末尾的的         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总和都存不***意字符串末尾多了个 ,所以这块判断是 <=【也就是=都算存不下】         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;  //内存总和减去标题字符串长度(不含字符串末尾的),剩余的大小,就是要memset的;     memset(ptmp,0,cha);       return; }

【main函数中调用(实战代码)】

//...其他代码...  //和设置标题有关的全局量 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中的测试代码片段】

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 查看进程信息

(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

  • 可见进程标题信息该变了。

本文转自互联网,侵权联系删除(C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » (C++通讯架构学习笔记):读配置文件、查泄漏、设置标题实战
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们