给传销产品做网站,淘宝客网站需要多大主机,洛阳做网站优化,手机网页视频提取工具文章目录 日志日志的创建logmessage 函数日志左边部分实现日志右边部分实现 完整代码log.hpp(整体实现)err.hpp (错误信息枚举#xff09; 守护进程PGID SID TTY 的介绍shell中控制进程组的方式结论 为什么要有守护进程存在#xff1f;守护进程的创建使用守护进程的条件守护进… 文章目录 日志日志的创建logmessage 函数日志左边部分实现日志右边部分实现 完整代码log.hpp(整体实现)err.hpp (错误信息枚举 守护进程PGID SID TTY 的介绍shell中控制进程组的方式结论 为什么要有守护进程存在守护进程的创建使用守护进程的条件守护进程化的函数自己实现守护进程化解决组长问题忽略信号处理 0 1 2 问题退出守护进程 完整代码err.hpp(错误信息枚举)daemon.hpp(整体实现) 日志
一般使用cout进行打印但是cout打印是不规范的 实际上 是采用日志进行打印的
日志的创建
创建一个 log.hpp 日志有自己的日志等级 通过枚举分别为 调试 常规 告警 一般错误 致命错误 未知错误 logmessage 函数
定义一个函数 logmessage参数level 为日志等级 为了按照可变参数的方式来进行格式化输出所以设置一个format 以及…可变参数(可以给c函数传递任意个数的参数)
日志左边部分实现
输入 man snprintf 将可变参数的内容显示到str字符串中 获取日志等级 设置一个字符串 level_string 通过tolevelstring函数 将数字转化为字符串 获取时间
输入 man localtime 将time_t转换为 struct tm 结构体类型 该结构体包含 秒 分 时 天 输入 man 3 time 通过gettime函数 获取时间 日志右边部分实现 为了处理可变参数部分所以使用vsprintf 输入 man snprintf 将写好的数据放到logRight中
完整代码
log.hpp(整体实现)
#pragma once
#includeiostream
#includestring.h
#includecstdio
#includecstring
#includecstdarg
#includeunistd.h
#includesys/types.h
#includetime.hconst std::string filenametecpserver.log;//日志等级
enum{DEBUG0, // 用于调试INFO , //1 常规WARNING, //2 告警ERROR , //3 一般错误FATAL , //4 致命错误UKNOWN//未知错误
};static std::string tolevelstring(int level)//将数字转化为字符串
{switch(level){case DEBUG : return DEBUG;case INFO : return INFO;case WARNING : return WARNING;case ERROR : return ERROR;case FATAL : return TATAL;default: return UKNOWN;}
}
std::string gettime()//获取时间
{time_t currtime(nullptr);//获取time_tstruct tm *tmplocaltime(curr);//将time_t 转换为 struct tm结构体char buffer[128];snprintf(buffer,sizeof(buffer),%d-%d-%d %d:%d:%d,tmp-tm_year1900,tmp-tm_mon1,tmp-tm_mday,tmp-tm_hour,tmp-tm_min,tmp-tm_sec);return buffer;}
void logmessage(int level, const char*format,...)
{//日志左边部分的实现char logLeft[1024];std::string level_stringtolevelstring(level);std::string curr_timegettime();snprintf(logLeft,sizeof(logLeft),%s %s %d,level_string.c_str(),curr_time.c_str());//日志右边部分的实现char logRight[1024]; va_list p;//p可以看作是1字节的指针va_start(p,format);//将p指向最开始vsnprintf(logRight,sizeof(logRight),format,p);va_end(p);//将指针置空//打印日志 printf(%s%s\n,logLeft,logRight);//保存到文件中FILE*fpfopen( filename.c_str(),a);//以追加的方式 将filename文件打开//fopen打开失败 返回空指针if(fpnullptr){return;}fprintf(fp,%s%s\n,logLeft,logRight);//将对应的信息格式化到流中fflush(fp);//刷新缓冲区fclose(fp);
}
err.hpp (错误信息枚举
#pragma once enum
{USAGE_ERR1,SOCKET_ERR,//2BIND_ERR,//3LISTEN_ERR,//4SETSID_ERR,//5OPEN_ERR//6
};
守护进程
网络服务一定在任何时候都能访问所以这个服务不能受任何用户的登录或者注销各种行为的影响 所以需要将进程进行守护进程化
PGID SID TTY 的介绍 在后台运行sleep 10000 PPID是bash的PID值 PGID是 进程组 (PGID相同就为同一个进程组,以从第一个进程进行命名) SID 是 会话ID TTY是 终端 若为?则说明跟终端没有关系若为具体的如pts/5则为终端文件 在终端2中输入在终端1中可以查看到 两者的PGID相同所以属于同一个进程组并且以sleep 1000 作为组长 通过查询会话ID 21668发现bash的PID PGUD SID 都为21668
shell中控制进程组的方式
查询后台任务 jobs 当再次输入sleep 5000 进行后台运行时发现前面的编号变成2 该编号为 任务编号 将某一任务提到前台运行 fg 任务编号 当把1号任务提到前台后再次使用jobs查询后台任务就查不到1号任务了 并且其他任务并不受影响 把2号任务提到前台使用 ctrl z 让服务暂停起来 在暂停后任务会自动切换到后台 输入 bg 2让2号任务在后台跑起来 结论
1. 进程组分为 前台任务 和 后台任务 在终端2中创建后台任务和前台任务在终端1中查询发现后台任务的(PGID)进程组 和 (SID)会话ID相同 而与后台的不同 2. 如果后台任务提到前台老的前天任务就无法运行 将任务编号为1的后台任务 使用 fg 提到前台后 输入 ls pwd 等 指令是没有作用的 会话中 只能有一个前台任务在运行 所以当 使用 ctrl c 将1号任务退出后bash把自己变成了前台任务所以又可以运行了
为什么要有守护进程存在 若登录就是创建一个会话启动进程会话内部有bash任务在当前会话中创建新的前后台任务那如果退出呢
当退出时就会销毁会话可能会影响会话内部的所有任务
网络服务器为了不受到用户登录注销的影响网络服务器 通常以守护进程的方式运行
守护进程的创建
输入 man 2 setsid 设置一个会话以进程组的组长ID作为新的会话ID
若返回成功则返回调用进程的PID若返回失败则返回-1并设置错误码 想要调用setsid不可以是组长
如在一家公司中你是组长有一天你想不干了 出去创业 是不可以的因为你手底下有一堆组员 所以要成功出去创业就必须卸任你的组长身份
使用守护进程的条件
1.忽略异常 2.对 0(标准输入) 1(标准输出) 2(标准错误) 作特殊处理 3.进程的工作路径 可能要更改 4.守护进程是一个全局的进程不想在某一个用户的目录下所以从整个系统中从最开始进行索引某些文件
守护进程化的函数
输入 man daemon提供守护进程化的函数 第一个参数表示 是否更改 工作目录默认不要改改为1表示为真 第二个参数表示 要不要关闭 0 1 2 默认不关
大部分情况下都是自己实现守护进程而不是调用该函数
自己实现守护进程化
解决组长问题 当启动时是在bash中新起一个任务只有一个进程自成进程组所以自成组长操作不被允许
成为组长的一般都是组中的第一个进程所以只需使其不为第一个进程即可 输入 man fork创建子进程 fork的返回值父进程返回子进程的PID值子进程返回0失败返回-1 当fork0时说明为父进程则让父进程退出只剩下子进程子进程不是进程的第一个也就不是组长就可以成功调用setsid
忽略信号
signal的第一个参数 表示 信号 第二个参数表示对指定动作的信号设定自定义处理动作 SIGPIPE 表示13号信号 SIG_IGN 为 自定义处理信号处理函数 把1强制转化成函数指针类型 即忽略信号
对13号信号 进行忽略 SIGCHLD信号 子进程在运行时会退出若父进程不关心子进程退出子进程就会变成僵尸状态 父进程要使用 wait/waitpid去等待子进程 回收僵尸获取子进程的退出结果 即父进程进行阻塞式等待(什么都不干就等待子进程的退出结果) 子进程要退出时会向父进程发信号 SIGCHLD
所以同样对 SIGCHLD信号 进行忽略 处理 0 1 2 问题
使用日志打印所以导致有很多输出结果但输出结果不想往显示器上面打印所以就需要处理标准输入 标准输出 标准错误 Linux系统提供一个 dev null的字符设备 向dev null 中写入都会被丢弃 从这个文件读什么都读不到 立马直接返回 输入 man 2 open打开文件 若返回成功则返回 文件描述符若返回失败则返回 -1 并将错误码返回 O_RDWR 读写的方式 重定向函数 输入 man dup2 可以直接将文件打开使用dup2重定向 输出重定向对应的文件描述符是1 假设其文件描述符是fd newfd为oldfd的一份拷贝最后只剩下oldfd dup2(fd,1) 即 将标准输出流 重定向到 文件描述符fd中 退出守护进程 输入 kill -9 守护进程的PID即可退出守护进程
完整代码
err.hpp(错误信息枚举)
#pragma once enum
{USAGE_ERR1,SOCKET_ERR,//2BIND_ERR,//3LISTEN_ERR,//4SETSID_ERR,//5OPEN_ERR//6
};
daemon.hpp(整体实现)
#pragma once
#includeunistd.h
#includesignal.h
#includesys/types.h
#includesys/stat.h
#includefcntl.h
#includelog.hpp
#includeerr.hppvoid Daemon()//自己实现服务器的守护进程化{//1.忽略信号signal(SIGPIPE,SIG_IGN);//忽略信号signal(SIGCHLD,SIG_IGN);//2.不要成为组长 if(fork()0)//说明为父进程则让父进程直接退出{exit(0);}//只剩下子进程//3.新建会话自己成为会话的话首进程pid_t retsetsid();if((int)ret-1)//守护进程失败{logmessage(FATAL,deamon error,code:%d,string :%s,errno,strerror(errno));exit(SETSID_ERR);//终止程序}//4.可以更改守护进程的工作路径//5.处理 0 1 2 问题int fdopen(/dev/null,O_RDWR);//以读写的方式打开字符设备if(fd0){logmessage(FATAL,deamon error,code:%d,string :%s,errno,strerror(errno));exit(OPEN_ERR);//终止程序} //将标准输入 输出错误 重定向到字符设备中dup2(fd,0);dup2(fd,1);dup2(fd,2);close(fd);}