02配置文件读取、程序运行标题设置、内存泄漏的检查工具

一、配置文件读取

使用配置文件,使服务器程序有了极大的灵活性,作为服务器程序开发者,必须要首先搞定的问题。
配置文件格式(内容)

#[开头的表示组信息,也等价于注释行
[Socket]
ListenPort = 5678    
DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g

读取配置文件类设计
PS:设计配置读取文件为类,准确的说单例类,采用的是单例设计模式

//类名可以遵照一定的命名规则规范
class CConfig
{
//---------------------------------------------------
private:
	CConfig();
public:
	~CConfig();
private:
	static CConfig *m_instance;
public:
	static CConfig* GetInstance()  // 单例设计模式  我们应该尽量第一次调用的时候在主线程中调用
	{
		if(m_instance == NULL) //之前没有创建的话就进入if中
		{
			//锁
			//std::lock_guard<std::mutex> sbguard(my_mutex);//在多线程中第一次调用单例类的时候,就需要加锁,这里使用lock_guard,其特性是{。。。}界定符内,构造函数加锁,析构函数解锁,my_mutex是自己定义的std::mutexl类型。C++的锁有很多种,其使用规则会在后序的其他文章中总结(暂)
			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; //存储配置信息的列表
};

成员函数的实现

//静态成员赋值
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=%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;
}

成员函数的调用

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

二、程序运行标题设置

仿照Nginx的官方源码

image

image

#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是因为末尾有\0,是占实际内存位置的,要算进来
    } //end for
    //这里无需判断penvmen == NULL,有些编译器new会返回NULL,有些会报异常,但不管怎样,如果在重要的地方new失败了,你无法收场,让程序失控崩溃,助你发现问题为好; 
    gp_envmem = new char[g_environlen]; //PS:需要释放
    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;
}

三、内存泄漏的检查工具

sudo apt-get install valgrind
memcheck的基本功能,能发现如下的问题;
a)使用未初始化的内存
b)使用已经释放了的内存
c)使用超过malloc()分配的内存
d)对堆栈的非法访问
e)申请的内存是否有释放*****
f)malloc/free,new/delete申请和释放内存的匹配
g)memcpy()内存拷贝函数中源指针和目标指针重叠;

实例:
所有应该释放的内存,都要释放掉,作为服务器程序开发者,要绝对的严谨和认真
格式:
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
image

posted @ 2022-03-04 10:45  豪崽_ZH  阅读(127)  评论(0)    收藏  举报