- 1. 创建守护进程
- 2. 外部传入参数解析以及环境变量类封装
- 3. 完成配置项
.yaml文件预加载 - 4. Application类封装
- C\C++知识点补充复习:
daemon()函数 - C\C++知识点补充复习:wait族函数
- include
- include
- C\C++知识点补充复习:
getenv()/setenv()函数 - C\C++知识点补充复习:
opendir()函数 - C\C++知识点补充复习:
readdir()函数 - C\C++知识点补充复习:
access()函数 - C\C++知识点补充复习:
lstat()函数 - C\C++知识点补充复习:
mkdir()函数 - C\C++知识点补充复习:
strdup()函数 - C\C++知识点补充复习:
strchr()/strrchr()函数
1. 创建守护进程
功能:将服务器框架以程序化的方式运行,设置一个守护进程。当负责服务器运行的进程挂了之后能够将其拉回重新运行起来。
1.2 记录父子进程的进程信息
功能:设置一个结构体专门用于记录服务器进程的启动时间、重启次数等相关信息。并将其置为单例模式来处理。
/*** @brief 服务器进程信息结构体*/struct ProcessInfo{pid_t parent_pid; //父进程PIDpid_t main_pid; //主进程PIDuint64_t parent_start_time = 0; //父进程开启时间uint64_t main_start_time = 0; //主进程开启时间uint32_t restart_count = 0; //服务器重启次数};//置为单例typedef Single<ProcessInfo> ProcessInfoMgr;
1.3 接口:start_daemon()函数
功能:负责启动服务器程序,可选择是否以守护进程方式启动。
/*** @brief 以守护进程的方式启动程序* @param[in] argc 传入参数的个数* @param[in] argv 传入参数的数值组* @param[in] main_cb 真正运行的函数* @param[in] is_daemon 是否以守护进程的方式启动* @return 返回程序执行结果*/int start_daemon(int argc, char* argv[],std::function<int(int argc, char*argv[])> main_cb, bool is_daemon){if(!is_daemon){return real_start(argc, argv, main_cb);}return real_daemon(argc, argv, main_cb);}
-
1.3.1
real_daemon()功能:开辟守护进程以及崩溃后重启
/*** @brief 开辟守护进程以及崩溃后重启* @param[in] argc 传入参数的个数* @param[in] argv 传入参数的数值组* @param[in] main_cb 真正运行的函数* @return 返回程序执行结果*/static int real_daemon(int argc, char* argv[],std::function<int(int argc, char* argv[])> main_cb){//开启守护daemon(1, 0);//获取当前进程(父进程)PIDProcessInfoMgr::GetInstance()->parent_pid = getpid();//获取当前进程(父进程)的启动时间ProcessInfoMgr::GetInstance()->parent_start_time = GetCurrentMs();while(1){pid_t pid = fork();if(pid == 0) //子进程{ProcessInfoMgr::GetInstance()->main_pid = getpid();ProcessInfoMgr::GetInstance()->main_pid = GetCurrentMs();KIT_LOG_INFO(g_logger) << "process start pid=" << getpid();return real_start(argc, argv, main_cb);}else if(pid > 0) //当前父进程{int status = 0;waitpid(pid, &status, 0);if(status){//服务器进程崩溃KIT_LOG_ERROR(g_logger) << "child process crash! pid=" << pid << ", status=" << status;}else{//服务器进程正常退出KIT_LOG_INFO(g_logger) << "child process finished! pid=" << pid;break;}ProcessInfoMgr::GetInstance()->restart_count += 1;//等待资源释放sleep(g_daemon_restart_interval->getValue());}else //出错{KIT_LOG_ERROR(g_logger) << "fork error ret=" << pid <<"errno=" << errno << ",is:" << strerror(errno);return -1;}}return 0;}
1.3.2 real_start()
功能:执行进程中需要执行的函数操作
/*** @brief 执行进程中真正运行函数* @param[in] argc 传入参数的个数* @param[in] argv 传入参数的数值组* @param[in] main_cb 真正运行的函数* @return 返回程序执行结果*/static int real_start(int argc, char* argv[],std::function<int(int argc, char* argv[])> main_cb){return main_cb(argc, argv);}
2. 外部传入参数解析以及环境变量类封装
功能:./kitserver -a -b -c xxx.conf。通过int argc, char* argv[]一些参数配置或者某个路径下配置文件来启动服务器。将key值和val值分离开存储,并对key值作一个补充的描述说明。另外,对程序运行的环境变量做一个管理和封装。
2.1 成员变量
class Env{...private://读写锁MutexType m_mutex;//参数集合std::map<std::string, std::string> m_args;//对参数的解释说明std::vector<std::pair<std::string, std::string> > m_helps;//当前运行程序名称std::string m_programName;//二进制可执行文件的绝对路径std::string m_exe;//二进制可执行文件的包含文件夹的绝对路径std::string m_cwd;};
2.2 核心接口
2.2.1 init()
功能:对外部传入的参数作解析分离。并且获取当前程序的可执行二进制文件路径的环境变量。
- 外部参数可能出现的情况:
-key1 val -key2-key1 -key2 val-key1 -key2-key
- 核心逻辑:
当前字符串的首字符是
"-"字符:表明这一项可能是一个-key值。进一步判断这一项的字符串长度是否合法,排除只有字符"-"的情况:- 合法,设置一个变量
now_key记录上一次轮询中-key值字符的首地址,判断当前该变量是否已经有值:- 有值,代表上一次的
-key值还未存储,调用add()加入到集合中 - 没有值,代表上一次分离出来的是
val值,已经存储过。
- 有值,代表上一次的
- 非法,报错返回
- 合法,设置一个变量
当前字符串的首字符不是
"-"字符:表明这一项可能是一个val值,看now_key是否有值:- 有值,说明上一次的
-key值是带val值的,它们需要配对加入 - 没有值,这是一个非法的参数,不能单独出现
val值
- 有值,说明上一次的
/*** @brief 解析传入的外部参数以及获取可执行文件的绝对路径* @param[in] argc 传入的参数个数* @param[in] argv 传入的参数数值组* @return 返回解析是否成功*/bool Env::init(int argc, char* argv[]){char link[1024] = {0};char path[1024] = {0};//从环境变量中拼装出所需的可执行文件的符号链接路径sprintf(link, "/proc/%d/exe", getpid());//通过符号链接路径读取出真正的路径if(readlink(link, path, sizeof(path)) < 0){KIT_LOG_ERROR(g_logger) << "readlink error, errno=" << errno<< ",is:" << strerror(errno);return false;}//拿到执行文件的绝对路径 /path/xxx/exem_exe = path;//把执行文件的所在文件夹路径截取出来auto pos = m_exe.find_last_of("/");m_cwd = m_exe.substr(0, pos) + "/";m_programName = argv[0];// -config /path/to/config -file xxxxxconst char* now_key = nullptr;//0号位置放的是执行程序名称for(int i = 1;i < argc;++i){//'-'开头表示是keyif(argv[i][0] == '-'){if(strlen(argv[i]) > 1){//当前key是否已经有值了if(now_key){//加入不带值的参数add(now_key, "");}//具体字符的首地址记录下now_key = argv[i] + 1;}else{KIT_LOG_ERROR(g_logger) << "invalid arg, index=" << i<< ", val=" << argv[i];return false;}}else //代表是参数的值val{//配置字符已经记录if(now_key){add(now_key, argv[i]);now_key = nullptr;}else{KIT_LOG_ERROR(g_logger) << "invalid arg, index=" << i<< ", val=" << argv[i];return false;}}}//当只有一个配置且没有val值 将其加入if(now_key)add(now_key, "");return true;}
2.2.2 setEnv()/getEnv()
功能:设置/获取当前程序的相关进程环境变量
/*** @brief 设置环境变量* @param name 环境变量的名称key值* @param val 环境变量的值val值* @return 返回是否设置成功*/bool Env::setEnv(const std::string& name, const std::string& val){return !setenv(name.c_str(), val.c_str(), 1);}/*** @brief 获取环境变量* @param name 环境变量的名称key值* @param default_val 环境变量的值val默认值=""* @return 返回环境变量对应val值字符串*/std::string Env::getEnv(const std::string& name, const std::string&default_val){const char* v = getenv(name.c_str());return v ? std::string(v) : default_val;}
3. 完成配置项.yaml文件预加载
功能:根据传入一个包含配置文件的文件夹相对路径/绝对路径预先加载.yaml配置文件,如lgo.yaml、http.yaml、tcp.yaml等等。
应用场景:可以添加一个定时器,周期性检查需要的配置项文件,发生改动就重新加载修改对应的配置项,实现实时加载的一种效果。
3.1 新增接口:LoadFromConfigDir()
功能:在Config类中新增一个接口,通过传入一个包含配置文件的文件夹相对路径/绝对路径以及目标文件后缀.yaml,就能自动的搜集文件夹及其子文件夹下包含所有目标文件的文件名。并且未发生修改的文件不会进行加载收集。
/*** @brief 记录配置文件最后的修改时间的容器*/static std::map<std::string, uint64_t> s_file2modify_time;/*** @brief 对修改和读取配置文件最后的修改时间的互斥锁*/static Mutex s_mutex;/*** @brief 以文件夹为单位加载配置文件* @param[in] path 配置文件夹的路径*/void Config::LoadFromConfigDir(const std::string& path){//转换为当前运行路径下的绝对路径std::string absolute_path = EnvMgr::GetInstance()->getAbsolutePath(path);std::vector<std::string> files;//找出后缀为.yaml的所有文件的路径+文件名 收集到vector中FSUtil::ListAllFile(files, absolute_path, ".yaml");//只有当配置文件真的发生修改才去加载这个文件//通过查看文件的最后修改时间实现for(auto &x : files){//读取文件信息struct stat st;if(lstat(x.c_str(), &st) < 0){KIT_LOG_ERROR(g_logger) << "lstat error, errno" << errno<< ", is:" << strerror(errno);return;}//加锁 读取和修改 文件最后修改时间{Mutex::Lock lock(s_mutex);//没有修改过就不进行加载if(s_file2modify_time[x] == (uint64_t)st.st_mtim.tv_sec)continue;s_file2modify_time[x] = (uint64_t)st.st_mtim.tv_sec;}try{//通过YAML的接口加载并解析文件YAML::Node root = YAML::LoadFile(x);LoadFromYaml(root);KIT_LOG_INFO(g_logger) << "load config file from .yaml OK, name=" << x;}catch(...){KIT_LOG_ERROR(g_logger) << "load config file error, name=" << x;}}}
4. Application类封装
功能:将服务器框架程序化包装。
4.1 成员变量
class Application{....private://传入参数个数int m_argc = 0;//传入参数数值组char **m_argv = nullptr;//记录不同类型的服务器std::vector<http::HttpServer::ptr> m_servers;//程序运行的调度器IOManager::ptr m_mainIOManager;//当前类指针static Application* s_instance;};
4.2 核心接口
4.2.1 init()
功能:初始化程序相关信息。包括:布置程序启动的参数说明、创建工作目录、创建pid文件等
/*** @brief 初始化程序* @param[in] argc 传入参数个数* @param[in] argv 传入参数数值组* @return 返回是否初始化成功*/bool Application::init(int argc, char* argv[]){m_argc = argc;m_argv = argv;//添加启动程序的相关指令说明Env* env = EnvMgr::GetInstance();env->addHelp("s", "start with the terminal");env->addHelp("d", "run as daemon");env->addHelp("c", "config file peth, default: ./conf");env->addHelp("h", "see help");bool is_print_help = false;if(!env->init(argc, argv))is_print_help = true;if(env->has("p"))is_print_help = true;//获取配置文件的文件夹路径 并且加载std::string conf_path = env->getConfigPath();KIT_LOG_INFO(g_logger) << "load config path:" << conf_path;Config::LoadFromConfigDir(conf_path);if(is_print_help){env->printHelp();return false;}//判断以什么方式启动程序int run_type = 0;if(env->has("s"))run_type = 1;else if(env->has("d"))run_type = 2;if(run_type == 0){env->printHelp();return false;}//拼装pid文件 = 工作路径+文件名称std::string pid_file = g_server_work_path->getValue() + "/" + g_server_pid_file->getValue();//服务器程序是否已经运行 避免重复打开if(FSUtil::IsRunningPidFile(pid_file)){KIT_LOG_ERROR(g_logger) << "server is running! pid_file=" << pid_file;return false;}//如果提供的工作路径不存在要创建一个if(!FSUtil::Mkdir(g_server_work_path->getValue())){KIT_LOG_ERROR(g_logger) << "creat work path error:[" << g_server_work_path->getValue() << ", errno=" << errno << ",is:" << strerror(errno);return false;}//打开pid文件 写入进程PID号std::ofstream ofs(pid_file);if(!ofs){KIT_LOG_ERROR(g_logger) << "open pid file error, file=" << pid_file;return false;}ofs << getpid();return true;}
4.2.2 run()
功能:开始运行服务器程序。可以选择是否是以守护进程的方式启动
/*** @brief 启动程序 可以选择是否是以守护进程的方式启动* @return 返回是否启动成功*/bool Application::run(){//看是否是以守护进程的方式启动bool is_daemon = EnvMgr::GetInstance()->has("d");return start_daemon(m_argc, m_argv, std::bind(&Application::main, this, std::placeholders::_1, std::placeholders::_2), is_daemon);}
4.2.3 main()
功能:真正执行服务器操作的函数。将当前执行服务器程序的子进程的PID写入pid文件,并使用IO调度器调度一个负责建立监听服务的函数/协程run_couroutine()。
/*** @brief 真正执行的main函数* @param[in] argc 传入参数个数* @param[in] argv 传入参数数值组* @return*/int Application::main(int argc, char* argv[]){std::string pid_file = g_server_work_path->getValue() + "/" + g_server_pid_file->getValue();//打开pid文件 写入进程PID号std::ofstream ofs(pid_file);if(!ofs){KIT_LOG_ERROR(g_logger) << "open pid file error, file=" << pid_file;return -1;}//需要实际执行服务器代码子进程的PIDofs << getpid();m_mainIOManager.reset(new IOManager("app", 1));m_mainIOManager->schedule(std::bind(&Application::run_coroutine, this));m_mainIOManager->stop();return 0;}
4.2.4 run_coroutine()
功能:负责执行某一个函数的协程以供调度。读取加载进来的服务器配置文件,根据配置文件中提供的服务器名称、服务器绑定地址、是否支持长连接、接收消息超时时间等来配置并启动一个服务器。
/*** @brief 负责执行函数的协程以供调度* @return*/int Application::run_coroutine(){auto server_conf = g_http_servers_conf->getValue();for(size_t i = 0;i < server_conf.size();++i){KIT_LOG_INFO(g_logger) << "server_conf[" << i << "]:\n" << LexicalCast<HttpServerConf, std::string>()(server_conf[i]);std::vector<Address::ptr> address;for(auto &x : server_conf[i].address){//判断是否是IP类地址size_t pos = x.find(":");if(pos == std::string::npos){KIT_LOG_ERROR(g_logger) << "invalid IPv4 address=" << x;continue;}//如果是IP类地址auto addr = Address::LookUpAny(x);if(addr){address.push_back(addr);continue;}//通过网卡获取地址std::vector<std::pair<Address::ptr, uint32_t> > result;if(!Address::GetInertfaceAddresses(result, x.substr(0, pos))){KIT_LOG_ERROR(g_logger) << "invlid address=" << x;continue;}//将获取到的地址设置端口号for(auto &i : result){auto ipaddr = std::dynamic_pointer_cast<IPAddress>(i.first);if(ipaddr)ipaddr->setPort(atoi(x.substr(pos + 1).c_str()));address.push_back(ipaddr);}}//创建HTTP服务器http::HttpServer::ptr http_server(new http::HttpServer(server_conf[i].keepalive));//为服务器绑定地址端口std::vector<Address::ptr> fails;if(!http_server->bind(address, fails)){for(auto& x : fails){KIT_LOG_ERROR(g_logger) << "bind address fail:" << *x;}_exit(0);}//启动服务器http_server->start();//设置服务器名称if(server_conf[i].name.size())http_server->setName(server_conf[i].name);//记录已经启动的服务器m_servers[server_conf[i].name].push_back(http_server);}return 0;}
C\C++知识点补充复习:daemon()函数
功能:用于希望将当前自身进程与控制终端分离并作为系统守护进程在后台运行的程序。
#include <unistd.h>int daemon(int nochdir, int noclose);
- 使用注意项:
- 如果
nochdir == 0,daemon()将进程的当前工作目录更改为根目录(”/“); 否则,当前工作目录保持不变。 - 如果
noclose == 0,daemon()将标准输入、标准输出和标准错误重定向到/dev/null; 否则,不会对这些文件描述符进行任何更改。
C\C++知识点补充复习:wait族函数
功能:用于等待调用进程的子进程的”状态变化”,并获取有关其状态已更改的子进程的信息。
- “状态变化”的几种情况:
- 子进程终止。注意:执行等待操作将会允许系统释放与子进程相关的资源;未执行等待,终止的子进程将会成为僵尸进程。(类似线程的
join()函数,回收线程资源) - 子进程被信号中断
- 子进程捕获一个信号并且恢复运行
```cpp
include
include
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
- 注意:对于`waitpid()`函数中传参`pid_t pid`有特殊点:1. `< -1` 表示等待进程组 ID 等于 pid 绝对值的任何子进程。1. `-1` 表示等待任何子进程。1. `0` 表示在调用 `waitpid()` 时等待其进程组 ID 等于调用进程的任何子进程。1. `> 0` 表示等待进程 ID 等于 pid 值的子进程。<a name="Dg4qb"></a># C\C++知识点补充复习:`readlink()`函数功能:将符号链接路径名(软链接)的内容放在用户指定的缓冲区中,以供使用。```cpp#include <unistd.h>ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);#include <fcntl.h> /* Definition of AT_* constants */#include <unistd.h>ssize_t readlinkat(int dirfd, const char *pathname,char *buf, size_t bufsiz);
C\C++知识点补充复习:getenv()/setenv()函数
功能:getenv() 函数搜索环境列表以查找环境变量名称,并返回指向相应值字符串的指针;setenv() 函数将变量 name添加到具有value的环境中
注意:对于setenv()函数,如果name不存在则直接添加;如果已经存在,要考虑是否将其现有的value值覆盖,取决于int overwrite变量,为1则覆盖,为0不发生改变并且返回成功状态。
#include <stdlib.h>int setenv(const char *name, const char *value, int overwrite);char *getenv(const char *name);
C\C++知识点补充复习:opendir()函数
功能:打开一个目录名对应的目录流,并返回一个指向目录流的指针。 流位于目录中的第一个条目。
返回值:返回一个指向目录流的指针。 出错时,返回 NULL,并适当设置 errno。
#include <sys/types.h>#include <dirent.h>DIR *opendir(const char *name);DIR *fdopendir(int fd);
C\C++知识点补充复习:readdir()函数
功能:通过一个文件夹路径,获取文件夹下的所有文件。
返回值:成功时,返回一个指向不同结构的指针。 (这个结构可能是静态分配的,不要试图释放它),注意:如果到达目录流的末尾,则返回 NULL并且不更改 errno。 如果发生错误,则返回 NULL 并适当设置 errno。 要区分流的结束和错误,请在调用 readdir() 之前将 errno 设置为零,然后如果返回 NULL,则检查 errno 的值。
#include <dirent.h>struct dirent *readdir(DIR *dirp);struct dirent {ino_t d_ino; /* Inode number */off_t d_off; /* Not an offset; see below */unsigned short d_reclen; /* Length of this record */unsigned char d_type; /* Type of file; not supportedby all filesystem types */char d_name[256]; /* Null-terminated filename */};
unsigned char d_type指示文件类型:DT_BLK这是一个块设备。DT_CHR这是一个字符设备。DT_DIR这是一个目录。DT_FIFO这是一个命名管道(FIFO)。DT_LNK这是一个符号链接。DT_REG这是一个常规文件。DT_SOCK这是一个 UNIX 域套接字。DT_UNKNOWN无法确定文件类型。
C\C++知识点补充复习:access()函数
功能:检查调用进程是否可以访问文件路径名。如果 pathname 是符号链接,则取消引用。
int mode:F_OK 测试文件是否存在。 R_OK、W_OK 和 X_OK 分别测试文件是否存在并授予读取、写入和执行权限。
返回值:成功时(授予所有请求的权限,或模式为F_OK 且文件存在),返回0。 出错时(mode 中的至少一位请求被拒绝的权限,或者 mode 是 F_OK 并且文件不存在,或者发生了其他错误),返回 -1,并适当地设置 errno。
#include <unistd.h>int access(const char *pathname, int mode);
C\C++知识点补充复习:lstat()函数
功能:返回文件相关信息,和stat()函数功能类似。如果 pathname是符号链接,则它返回有关链接本身的信息,而不是它所引用的文件。
返回值:成功时,返回0。 出错时,返回 -1,并适当设置 errno。
#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>int stat(const char *pathname, struct stat *statbuf);int fstat(int fd, struct stat *statbuf);int lstat(const char *pathname, struct stat *statbuf);
C\C++知识点补充复习:mkdir()函数
功能:尝试创建一个名为pathname的目录。
返回值:成功时返回零,如果发生错误则返回 -1(在这种情况下,errno 设置得当)
#include <sys/stat.h>#include <sys/types.h>int mkdir(const char *pathname, mode_t mode);
mode_t mode:默认是0777全局访问权限
The following mask values are defined for the file mode component of the st_mode field:S_ISUID 04000 set-user-ID bit (see execve(2))S_ISGID 02000 set-group-ID bit (see below)S_ISVTX 01000 sticky bit (see below)S_IRWXU 00700 owner has read, write, and execute permissionS_IRUSR 00400 owner has read permissionS_IWUSR 00200 owner has write permissionS_IXUSR 00100 owner has execute permissionS_IRWXG 00070 group has read, write, and execute permissionS_IRGRP 00040 group has read permissionS_IWGRP 00020 group has write permissionS_IXGRP 00010 group has execute permissionS_IRWXO 00007 others (not in group) have read, write, and ex‐ecute permissionS_IROTH 00004 others have read permissionS_IWOTH 00002 others have write permissionS_IXOTH 00001 others have execute permission
C\C++知识点补充复习:strdup()函数
功能:返回一个指向新字符串的指针,该字符串是const char* s 的副本。 新字符串的内存使用 malloc() 获得,并且可以使用 free() 释放。
返回值:成功时,返回一个指向复制字符串的指针。 如果可用内存不足,则返回 NULL,并设置 errno 以指示错误的原因。
#include <string.h>char *strdup(const char *s);char *strndup(const char *s, size_t n);char *strdupa(const char *s);char *strndupa(const char *s, size_t n);
C\C++知识点补充复习:strchr()/strrchr()函数
功能:strchr()函数返回一个指针,指向字符串 s中字符 c 的第一次出现。
strrchr()函数返回一个指针,指向字符串 s中字符 c 的最后一次出现。
返回值:返回一个指向匹配字符的指针,如果找不到该字符,则返回 NULL。
注意:终止的空字节被认为是字符串的一部分,因此如果c 被指定为 ‘\0’,这些函数将返回一个指向终止符的指针。
#include <string.h>char *strchr(const char *s, int c);char *strrchr(const char *s, int c);
