日志
//日志就是服务器在运行的时候要定期的把执行痕迹保留下来
#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
// 日志是有日志等级的,有的报错无关紧要,有的日志可以反应很严重的错误
const std::string filename = "log/tcpserv er.log";//日志位于当前目录下的log文件夹内部
enum
{
Debug = 0,//用于调试
Info,//常规等级,正常显示
Warning,//告警,并不是错误
Error,//一般错误,不影响继续向后运行
Fatal,//致命的错误
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";//这种出错一般不会影响代码的后续运行,比如read的返回值小于0
case Fatal:
return "Fatal";//这种错误发生一般会直接exit或者return,比如说创建套接字失败。
default:
return "Uknown";
}
}
static std::string getTime()
{
time_t curr = time(nullptr);//拿到当前的时间
struct tm *tmp = localtime(&curr);//将time_t类型转换成为一个结构体
char buffer[128];
snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon+1, tmp->tm_mday,
tmp->tm_hour, tmp->tm_min, tmp->tm_sec);//tm结构体中关于年的成员是减去了1900的
return buffer;
}
// 日志格式: 日志等级 时间 pid 消息体
// logMessage(DEBUG, "hello: %d, %s", 12, s.c_str()); // DEBUG hello:12, world
void logMessage(int level, const char *format, ...)//
{
char logLeft[1024];
std::string level_string = toLevelString(level);
std::string curr_time = getTime();
snprintf(logLeft, sizeof(logLeft), "[%s] [%s] [%d] ", level_string.c_str(), curr_time.c_str(), getpid());//int snprintf(char *str, size_t size, const char *format, ...);将对应的格式信息写入到logleft当中。
//消息体
char logRight[1024];
va_list p;
va_start(p, format);//让p指向可变参数的起始部分
vsnprintf(logRight, sizeof(logRight), format, p);//这里不采用遍历字符串找%d、%f来进行分析,也就是va_arg的方式,int vsnprintf(char *str, size_t size, const char *format, va_list ap);
va_end(p);
// 打印
// printf("%s%s\n", logLeft, logRight);
// 将日志保存到文件中,因为当该进程变成守护进程的时候,这些信息无法打印到显示器上了。
FILE *fp = fopen(filename.c_str(), "a");
if(fp == nullptr)return;
fprintf(fp,"%s%s\n", logLeft, logRight);
fflush(fp); //刷新,可写也可以不写,因为fclose也会刷新。
fclose(fp);
// 预备
// va_list p; // char *类型,也就是定义一个指针
// int a = va_arg(p, int); // 根据类型提取参数,我如何知道类型的呢,那就是根据format当中的%d、%p,这些就是我们所说的数据类型。
// va_start(p, format); //p指向可变参数部分的起始地址,可变参数一定和前一个参数format是挨着的,相当于对format取地址然后进行偏移从而取到可变参数的起始地址
// va_end(p); // p = NULL;
//我们可以通过上面的一个变量和三个宏函数来提取可变参数的各个部分
}
守护进程
PGID是进程组,SID是会话id,TTY指的是终端文件,?指的是对应进程和终端没有关系,而pts/3指的是终端文件,说明sleep 10000这个进程和该终端文件时关联的,也就是该进程打开了这个终端。所以我们在执行ls、pwd的时候,会有对应的信息打印在该终端设备上,原因是因为进程运行时把该终端文件打开了,然后把对应的内容朝该终端文件中进行了写入。
会话>=进程组>=进程,固定的会话会关联一个固定的终端文件,进程组长就是多个进程当中的第一个,而会话进程也是一个bash进程。
如果我们用xshell再次登录我们的Linux系统,本质上我们的Linux要帮我们创建另外一个会话,未来在新的终端执行命令时,也是在这个会话里面运行的。
jobs查看该会话内部的所有任务,fg可以将任务切换到前台,前台任务进行ctrl + z将可以暂停该任务,然后bg就可以让任务继续在后台运行。这就是shell中控制进程组的方式。
1、进程组分为前台任务和后台任务
2、如果后台任务提到前台,也就是谁当前使用该终端,老的前台任务bash就无法运行了。ctrl+c可以干掉该任务然后让bash重新回到前台运行
3、任何时刻,在会话中只能有一个前台任务在运行!所以我们在命令行启动一个前台进程之后,bash就无法运行了。
4、如果登录就是创建一个会话,bash任务,启动我们的进程,就是在当前会话中创建新的前后台任务,那么如果我们退出呢就是销毁该会话,销毁会话就可能会影响会话内部的所有任务。
一般网络服务器,为了不受到用户登注销的影响,就需要让该进程该会话中独立出来,不要跟任何一个用户会话产生关联,独立成为一个会话,从包含关系变成并列关系,就有了守护进程的运行方式。 pid_t setsid(void)谁调用该函数就把自己设立成为一个新的会话,成功了就返回调用进程的pid。
为什么要有进程组呢?进程组是用来完成任务的,而任务
#pragma once
// .\xxx是在前台运行,而.\xxx &是在后台运行
// 1. setsid();
// 2. setsid(), 调用进程,不能是组长!我们怎么保证自己不是组长呢?
// 3. 守护进程a. 忽略异常信号 b. 对于标准输入0,标准输出1,标准错误2要做特殊处理 c. 进程的工作路径可能要更改 /,也就是守护进程是一个全局进程,不想在某个用户文件夹下
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "log.hpp"
#include "err.hpp"
//守护进程的本质:是孤儿进程的一种!
void Daemon()
{
// 1. 忽略信号,信号的忽略行为会被子进程继承
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
// 2. 让自己不要成为组长
if (fork() > 0)//fork大于0就是父进程。于是直接退出就可以了。所以守护进程的本质:是孤儿进程的一种!
exit(0);
// 3. 新建会话,自己 成为会话的话首进程
pid_t ret = setsid();//子进程执行setsid的时候,就不是进程组的第一个了。
if ((int)ret == -1)
{
logMessage(Fatal, "deamon error, code: %d, string: %s", errno, strerror(errno));
exit(SETSID_ERR);
}
// 4. 可选:可以更改守护进程的工作路径
// chdir("/")
// 5. 处理后续的对于0,1,2的问题。已经守护进程化了,不想还往显示器进行打印,简单粗暴的做法就是直接close掉,万一代码里面有cin、cout就会直接报错。
int fd = open("/dev/null", O_RDWR);
if (fd < 0)
{
logMessage(Fatal, "open error, code: %d, string: %s", errno, strerror(errno));
exit(OPEN_ERR);
}
dup2(fd, 0);//把0、1、2重定向到这个信息黑洞当中,直接丢弃掉。
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
/dev/null是一个字符设备,往其打印内容或者是从其获取内容都是没有任何反应的。 守护进程一启动不会阻塞在前台,而且把自己单独拎出去了,和当前会话就没有关系了。我们通过jobs在该会话中是看不到的,并且通过ps ajx查看该进程会发现它的TTY会变成?,并且自称进程组、自称会话。现在将xshell关闭,该进程仍然可以运行,因为它是守护进程。现在jobs是看不到了,因为他已经独立会话了,不属于我这个会话了。想要关闭该进程需要通过kill。