在前面我们写tcp的CS模型中我们不难知道,每次出现了一些错误信息时我们都是将其打印在了终端上面,但是这种方式在实际中是不可取的,而我们就要将其中的信息记录在一个文件中方便我们查看,所以我们还得再加一个简单的日志(ps:博主这里写的日志是一个简易版本的日志系统,实际上如果想完成一个较为完整的日志系统比这里难度要大得多)而日志中的信息我们也习惯将其分一下类:
enum
{
Debug = 0,
Infor,
Warnning,
Error,
Fatal,
Unkonwn
};
各种信息的命名顾名思义,有了这个理解后我们再来编写代码就会轻松得多。
#pragma once
#include
#include
#include
#include
#include
#include
#include "err.hpp"
using namespace std;
const char* logname="log.txt";
static string GetLev(int lev)
{
switch(lev)
{
case 0:
return "Debug";
case 1:
return "Infor";
case 2:
return "Warnning";
case 3:
return "Error";
case 4:
return "Fatal";
default:
return "Unkonwn";
}
}
static string GetTime()
{
// struct tm {
// int tm_sec; /* seconds */
// int tm_min; /* minutes */
// int tm_hour; /* hours */
// int tm_mday; /* day of the month */
// int tm_mon; /* month */
// int tm_year; /* year */
// int tm_wday; /* day of the week */
// int tm_yday; /* day in the year */
// int tm_isdst; /* daylight saving time */
// };
time_t cur=time(nullptr);
tm* pt=localtime(&cur);
char buffer[1024]={0};
snprintf(buffer,sizeof(buffer),"%d-%d-%d:%d:%d:%d",pt->tm_year+1900,pt->tm_mon+1,
pt->tm_mday,pt->tm_hour,pt->tm_min,pt->tm_sec);
return buffer;
}
void LogMessage(int level, const char *format, ...)
{
char Llog[1024] = {0}; // 记录一些基本的日志消息
string lev = GetLev(level);
string time = GetTime();
snprintf(Llog, sizeof(Llog), "[%s] [%s] [%d] ", lev.c_str(), time.c_str(), getpid());
char Rlog[1024]={0};//记录用户添加的信息,可以让用户自行调整
va_list p;
va_start(p,format);
vsnprintf(Rlog,sizeof(Rlog),format,p);
va_end(p);
//将日志信息保存在文件中
FILE* fp=fopen(logname,"a");
if(fp==nullptr)
exit(-1);
fprintf(fp,"%s%s\n",Llog,Rlog);
fclose(fp);
}
代码中其他地方还好,这里博主主要讲解下在LogMessage接口中使用的可变参数列表,大家可以简单的这么理解下面的这些:
va_list p; // char *
int a = va_arg(p, int); // 根据类型提取参数
va_start(p, format); //p指向可变参数部分的起始地址
va_end(p); // p = NULL;
然后再回去看上面的代码就会轻松很多了。
想要了解更多关于日志系统的介绍与实现可以参考博主另外的一篇文章:
【日志系统】
在讲解守护进程前我们先来了解下进程组,会话的基本理解。
首先我们创建2个前台进程:
sleep 1000 | sleep 2000
sleep 1000 | sleep 2000 &
从上面的图中我们了解过PPID PID,而PGID和SID就是我们马上要讲解的进程组和会话。从上面的图中我们不难发现,进程组有3个,进程会话只有两个,我们刚创建的6个进程都隶属于同一个会话,会话中又包含了两个进程组,所以我们不难得出下面的关系:
会话>=进程组>=进程
而进程组的组长就是我们第一个创建的进程,从上面图中我们也可以很好的得到验证.
我们可以查看4853进程是哪一个?
验证得知是bash
进程。
像上面我们创建一个新的页面就会打开一个新的会话,会话中有着很多任务(进程组).
我们可以使用jobs
来查看进程组以及任务号。比如我们再创建几个后台进程:
其中第一列上面的数字就叫做任务编号。
那假如我们想让某一个任务从后台进程变成前台进程呢?我们可以使用fg
命令:
当我们使用了fg
命令将3号任务变成前台进程时,我们发现该终端已经不能够再显示命令提示符了(也就是bash
不能够运行了),为什么呢?
因为一个会话中只能有一个前台进程在运行,而当前该会话中已经有了3号进程在运行,所以自然bash就不能够正常运行了,当我们把该进程Ctrl+C
时:
bash又能够正常运行了。
那我们并不想让后台进程变成前台进程呢?我们可以使用Ctrl+Z
,然后再用bg
命令来继续让后台命令运行:
所以我们不难得出结论:登录就是创建一个会话,bash
任务会启动我们创建的进程,也就是在当前会话中创建新的任务;那么当我们退出登录是就要销毁该会话中所有的任务。
所以我们在实现TCP服务器的时候,我们应该让服务器拥有独立的会话,否则的话当我们关闭了服务器后就不能够正常服务了。
那么我们应该怎样让普通进程变成守护进程呢?
最主要的接口就是setsid
.
其实系统也给我们提供了daemon
接口,但是我们不使用这个接口,而是自己手动写一个守护进程。
daemon.hpp:
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "messageLog.hpp"
#include"err.hpp"
void Daemon()
{
// 1 忽略信号
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
// 2 让自己不要成为组长(守护进程不能够是组长)
if (fork() > 0)
exit(0);
// 3. 新建会话,自己成为会话的话首进程
pid_t ret = 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的问题
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);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
其中代码中的每一步我都给予了详细的说明,这里需要额外讲解下的就是/dev/null
,大家可以把这个文件理解为一个黑洞,就是可以把你的任何数据都丢到里面,但是不会给你保存,丢进去了就找不到了。
此时我们来验证下:
当我们运行时我们发现并不会像以前那样卡在那里等待着客户端,其实这个很好理解,因为该进程已经成为了守护进程,已经有了独立会话所以自然不会显现。
我们可以查询一下该进程;
我们使用客户端来访问时:
不难发现也是成功了的。
但是这时大家或许会问了,那你这样不是无敌了嘛,那我想干掉守护进程咋办?其实大家不要担心,杀人不眨眼的kill -9
可以直接干掉守护进程。
到目前为止,守护进程的工作已经做得差不多了。