【计算机网络学习之路】日志和守护进程

news2024/12/24 2:19:16

文章目录

  • 前言
  • 一. 日志介绍
  • 二. 简单日志
    • 1. 左字符串
    • 2. 右字符串
  • 三. 守护进程
    • 1. ps -axj命令
    • 2. 会话
      • 扩展命令
    • 3. 创建守护进程
  • 结束语

前言

本系列文章是计算机网络学习的笔记,欢迎大佬们阅读,纠错,分享相关知识。希望可以与你共同进步。

本篇博客介绍简单,较为基础的日志。

日志和守护进程都是辅助服务器的,一个是服务器的运行信息,一个是服务器的运行方式

一. 日志介绍

日志是记录事件,运行结果的工具
日志文件是重要的系统文件,其中记录了很多重要的系统运行的事件。包括用户的登录信息,系统的启动信息,系统的安全信息,各种服务相关信息
日志对于安全来说也很重要,它记录了每天系统发生的各种事情,通过日志来检查错误发送的原因,或受到攻击时攻击者留下的痕迹

日志管理服务

日志级别分为:

debug有调试信息的,日志通信最多
info一般信息日志,最常用
notic最具有重要性的普通条件的信息
warning警告级别
err错误级别,组织某个功能或者模块不能正常工作的信息
crit严重级别,阻止整个系统或者整个软件不能正常工作的信息
alert需要立刻修改的信息
emerg内核崩溃等重要信息
fatal致命错误
none什么都不记录

注意: 从上到下,级别从低到高,记录信息越来越少

二. 简单日志

本篇博客的日志是以函数的形式完成的,调用方式如下:

logMessage(Warning,"read error,%d,errno:%d",strerror(errno),errno);

参数有三个:日志级别,格式控制,可变参数

对应如下:

void logMessage(int level,const char*format,...){}

注意:format类型需要时const char*,因为大部分是以常量字符串的形式传参

我们期望最后的日志信息是这样的:[日志级别] [时间] [进程号] 消息内容(format)

可以将日志信息分成两部分,左字符串和右字符串,前三个为一组,消息内容使用vsnprintf

1. 左字符串

#pragma once

#include<iostream>
#include<string>
#include<ctime>
#include<unistd.h>
#include<sys/types.h>
#include<stdarg.h>

enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal,
    Uknown
};
//获取日志等级字符串
static std::string getLevelString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    default:
        return "Uknown";
    }
}
//获取时间字符串
static std::string gettime()
{
    time_t cur=time(nullptr);
    struct tm* tmp=localtime(&cur);
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"%d-%d-%d %d:%d:%d",tmp->tm_year+1,tmp->tm_mon+1,tmp->tm_mday+1,tmp->tm_hour,tmp->tm_min,tmp->tm_sec);
    return buffer;
}
//日志信息
void logMessage(int level,char*format,...)
{
    char logLeft[1024];
    std::string level_string=getLevelString(level);//日志级别
    std::string time_string=gettime();//时间
    std::string pid_string=std::to_string(getpid());//进程号
	//[日志级别] [时间] [进程号]
    snprintf(logLeft,sizeof(logLeft),"[%s] [%s] [%s] ",level_string.c_str(),time_string.c_str(),pid_string.c_str());
}

2. 右字符串

接下来介绍va系列——解析可变参数
在这里插入图片描述

在C语言学习中,函数的调用会创建栈帧,而函数传参会进行压栈,可变参数也是如此,所以可变参数的前一个参数的后一位就是可变参数的起始地址

  • va_list:类似指针,可以指向可变参数的起始和结束,可以遍历可变参数
  • va_start:将va_list定位到last后面
  • va_arg:将从va_list开始的数据,按照type类型进行提取返回
  • va_end:清空va_list
  • va_copy:将src拷贝给dest

而vsnprintf可以帮我们遍历可变参数,不需要我们自己控制

vsnprintf()

vsnprintf是一个标准的 C 函数,用于格式化字符串并将生成的字符存储在缓冲区中。它与函数类似,但有一个关键区别:函数不是直接采用可变长度的参数列表,而是采用参数,该参数是已使用宏初始化的参数列表。

以下是该函数的工作原理:

在这里插入图片描述

  • str:写入的缓冲区
  • size:缓冲区大小
  • format:格式控制
  • ap:va_list 指向可变参数的指针

使用如下:

const std::string filename="./log/tcpserver.log";

void logMessage(int level,char*format,...)
{
    char logLeft[1024];
    std::string level_string=getLevelString(level);
    std::string time_string=gettime();
    std::string pid_string=std::to_string(getpid());

    snprintf(logLeft,sizeof(logLeft),"[%s] [%s] [%s] ",level_string.c_str(),time_string.c_str(),pid_string.c_str());

    char logRight[1024];
    va_list p;//类似指针
    va_start(p,format);//将p定位到可变参数首地址
    vsnprintf(logRight,sizeof(logRight),format,p);//按format格式将可变参数写入logRight
    va_end(p);//将va_list清理
    
	汇总两个字符串,打印
    //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(fp);
}

完整代码如下:
log.hpp

#pragma once

#include<iostream>
#include<string>
#include<ctime>
#include<unistd.h>
#include<sys/types.h>
#include<stdarg.h>

const std::string filename = "./log/tcpserver.log";

enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal,
    Uknown
};

static std::string getLevelString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    default:
        return "Uknown";
    }
}

static std::string gettime()
{
    time_t cur=time(nullptr);
    struct tm* tmp=localtime(&cur);
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"%d-%d-%d %d:%d:%d",tmp->tm_year+1,tmp->tm_mon+1,tmp->tm_mday+1,
                                    tmp->tm_hour,tmp->tm_min,tmp->tm_sec);
    return buffer;
}

//日志组成:日志等级 时间 PID 消息内容

//日志等级 格式控制 可变参数
void logMessage(int level,char*format,...)
{
    char logLeft[1024];
    std::string level_string=getLevelString(level);
    std::string time_string=gettime();
    std::string pid_string=std::to_string(getpid());

    snprintf(logLeft,sizeof(logLeft),"[%s] [%s] [%s] ",level_string.c_str(),time_string.c_str(),pid_string.c_str());

    char logRight[1024];
    va_list p;//类似指针
    va_start(p,format);//将p定位到可变参数首地址
    vsnprintf(logRight,sizeof(logRight),format,p);//按format格式将可变参数写入logRight

    va_end(p);//将va_list清理

    打印日志信息
    //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(fp);
}

三. 守护进程

服务器一方面需要24小时不间断运行,另一方面还需要不被其他程序所影响,更准确点,是避免被其他程序的任何终端所产生信息所打断。这就要求服务器要守护进程化

守护进程

守护进程,也就是通常说的 Daemon进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存周期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件

守护进程本质是孤儿进程,脱离终端。避免被任何终端所产生的信息所打断,其在执行过程的信息也不在任何终端上显示,一般是是使用日志文件持久化信息。
由于在Linux中,每一个系统与用户进行交流的界面被称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程就会自动关闭

Linux的终端文件路径一般在 /dev中
在这里插入图片描述

1. ps -axj命令

使用ps -axj命令查看进程

在这里插入图片描述

  • PPID:父进程进程ID
  • PID:进程ID
  • PGID:进程组ID
  • SID:会话ID
  • TTY:控制终端;?表示没有控制终端
  • TPGID:终端进程组ID
  • STAT:状态

    R:进程正在运行或在运行队列中等待
    S:进程处于休眠状态,等待某个条件的形成或接收到信号。S+代表前台运行s表示会话领导Ss表示既在休眠,又是会话领导
    D:进程不可中断状态,收到信号不唤醒和不可运行,进程必须等待直到有中断发生,通常是IO
    Z:进程已终止,但进程描述符存在,直到父进程调用pidwait()回收后释放。僵尸进程
    T:进程已停止,进程收到SIGSTOP,SIGSTP,SIGTIN,SIGTOU信号后停止运行
    X:已经死掉的进程

  • UID: 执行者身份
  • COMMAND:程序名/运行该程序的指令

2. 会话

在ps -axj中可以看到有进程组ID和会话ID。二者分别是什么呢

在这里插入图片描述

可以看到,一条命令的三个sleep形成了三个进程,进程号依次递增
PGID(进程组号)相同,和第一个进程ID相同;SID(会话ID)相同,终端文件相同,TPGID(终端进程组)=PGID

再起三个sleep任务

在这里插入图片描述

可以看到PGID不同,SID相同

  • 会话 >= 进程组 >= 进程
  • 会话关联一个终端文件
  • 进程组的组长,都是多个进程中的第一个

扩展命令

接下来介绍一些命令:

ctrl+z:将当前前台进程调到后台,并停止
在这里插入图片描述

jobs:查看当前会话的后台任务,会话的概念稍后讲解
jobs只能查看本会话的后台任务,无法查看其他会话的后台任务
在这里插入图片描述

fg+任务号:将后台程序调到前台运行
在这里插入图片描述

bg+任务号:让后台停止的任务开始运行

在这里插入图片描述


接下来回归会话的讲解

当我们通过Xshell连接云服务器时,我们登录成功,会为本次登录创建一个会话。每一次登录成功,都会创建会话。每个会话会关联一个终端文件。当我们退出时,其实只是将该会话资源回收
其中创建的进程组和内部进程,都是在当前会话中。因为一个会话只有一个控制终端,所以如果后台任务提到前台,老的前台任务就无法运行
一个会话只能有一个前台任务在运行

在这里插入图片描述

而退出会销毁会话,所以如果运行服务器的会话关闭,那么服务器也会停止运行。
所以,一般的网络服务器,为了不受到用户的登录注销等其他影响,网络服务器都需要以守护进程的方式进行

3. 创建守护进程

守护进程本质是自成会话的孤儿进程
而一个进程要想成为守护进程,需要满足以下几个要求:

  1. 不能是进程组组长。因为要调用函数独立出去,如果进程组组长,则会影响进程组的其他进程
  2. 需要忽略异常信号
  3. 读写错误输入输出需要特殊处理
  4. 进程的工作路径可能要改

新建会话的函数:setsid()
在这里插入图片描述
返回值:成功返回新的进程ID,失败返回-1并设置错误码

哪个进程调用这个函数,哪个进程的的资源就会被转移到这个自成会话的进程

代码如下:
daemon.hpp

#pragma once 

#include<cerrno>
#include<cstdlib>
#include<cstring>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#include"log.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,%s,errno:%d",strerror(errno),errno);
        exit(1);
    }

    //4.可选,更改守护进程的工作路径
    //chdir("/");

    //5.处理后续的读写错误——文件描述符0,1,2
    int fd=open("/dev/null",O_RDWR);
    if(fd<0)
    {
        logMessage(Fatal,"open null error,%s,errno:%d",strerror(errno),errno);
        exit(2);
    }
    //将0,1,2的内容输入到null文件
    dup2(fd,0);
    dup2(fd,1);
    dup2(fd,2);
    close(fd);
}

/dev/null是一个文件,像是一个黑洞一样,扔进去的数据不会显示,也从里面读不到数据
在这里插入图片描述
正好可以将读写错误——0,1,2文件的输入输出数据丢进这个黑洞文件中。

但如此,日志系统就不能以打印显示日志信息,而需要创建一个日志文件,将日志信息写入文件中进行持久化。如此也不会影响其他进程

结束语

本篇博客到此结束,感谢看到此处。
欢迎大家纠错和补充
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1251352.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java springboot测试类虚拟MVC环境 匹配请求头指定key与预期值是否相同

上文 java springboot测试类虚拟MVC环境 匹配返回值与预期内容是否相同 (JSON数据格式) 版 中 我们展示 json匹配内容的方式 那么 本文我们来看看Content-Type属性的匹配方式 首先 我们从返回体可以看出 Content-Type 在请求头信息 Headers 中 我们直接将测试类代码更改如下 …

[极客大挑战 2019]Secret File1

[极客大挑战 2019]Secret File1 在bp里面发现secr3t.php 将secr3t.php 直接加在网站后面&#xff0c;发现了有关flag的信息&#xff0c;一个flag.php文件 在遇到flag.php时候&#xff0c;联想到php伪协议&#xff0c;构造伪协议方式 secr3t.php?filephp://filter/readconver…

Pygame游戏实战五:拼图游戏

介绍模块 本游戏使用的是由Pycharm中的pygame模块来实现的&#xff0c;也可以在python中运行。通过Pygame制作一个拼图游戏&#xff0c;将一个完整的图片进行随机切分&#xff0c;在将其进行还原成完整的图像&#xff0c;看看这个是你小时候玩的游戏吗&#xff1f; 最小开发框…

微信小程序 服务端返回富文本,图片无法显示

场景&#xff1a;   微信小程序开发中&#xff0c;需要从服务端拿取数据渲染到页面上&#xff0c;后台返回的富文本里&#xff0c;图片路径有时是没有带域名前缀的&#xff0c;导致图片无法正常显示。 解决方案&#xff1a;   在富文本返回时&#xff0c;用正则匹配&#…

Linux CentOS7 LVM

LVM&#xff08;Logical Volume Manger&#xff09;逻辑卷管理&#xff0c;Linux磁盘分区管理的一种机制&#xff0c;建立在硬盘和分区上的一个逻辑层&#xff0c;提高磁盘分区管理的灵活性。物理设备&#xff0c;是用于保留逻辑卷中所存储数据的存储设备。它们是块设备,可以是…

Python---函数定义时缺省参数(参数默认值)---放最右边

缺省参数也叫默认参数&#xff0c;用于定义函数&#xff0c;为参数提供默认值&#xff0c;调用函数时 可 不传该默认参数的值&#xff08;注意&#xff1a;所有位置参数必须出现在默认参数前&#xff0c;包括函数定义和调用&#xff09;。 比如&#xff1a;原先的代码&#…

相比其他关系型数据库,AntDB JDBC驱动特性有哪些不同之处

摘要&#xff1a;使用Java语言进行各类应用程序的快速开发成为目前比较主要且流行的开发方式。JDBC是 Java 语言中用来连接和操作关系型数据库的 API&#xff0c;在业务程序与关系型数据库通信时&#xff0c;必然会使用JDBC驱动。 本文将通过国产关系型数据库AntDB中的JDBC为大…

LLaMA 2:开源的预训练和微调语言模型推理引擎 | 开源日报 No.86

facebookresearch/llama Stars: 36.0k License: NOASSERTION LLaMA 2 是一个开源项目&#xff0c;用于加载 LLaMA 模型并进行推理。 该项目的主要功能是提供预训练和微调后的 LLaMA 语言模型的权重和起始代码。这些模型参数范围从 7B 到 70B 不等。 以下是该项目的关键特性…

2024年天津天狮学院专升本护理学专业《护理学基础》考试大纲

天津天狮学院2024年护理学专业高职升本入学考试《护理学基础》考试大纲 一、考试性质 《护理学基础》专业课程考试是天津天狮学院护理专业高职升本入学考试的必考科目之一&#xff0c;其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。 《护理学基础》考…

歌手荆涛演唱歌曲《老板的孤独》:揭示风光背后的真实心境

由歌手荆涛演唱的《老板的孤独》不仅是一首歌&#xff0c;更像是一部情感的长篇。每一句歌词都深深由触动了那些身处领导位置、背负众多期待与责任的“老板”们。被喊一声“老板”&#xff0c;背后蕴藏的是无尽的压力与孤独&#xff0c;是对自己、对家人、对团队的责任与担当。…

dat文件转换成excel教程

dat文件存在于很多的日用场合&#xff0c;为了更好的去进行办公使用&#xff0c;很多的用户都会将dat文件转换成excel&#xff0c;但是不知道怎么操作的却很多&#xff0c;下面来看看教程吧。 dat文件转换成excel&#xff1a; 1、首先打开excel&#xff0c;然后点击上面的“数…

Python武器库开发-前端篇之CSS基本语法(三十)

前端篇之CSS基本语法(三十) CSS简介 CSS&#xff08;层叠样式表&#xff09;是一种用于描述网页外观和布局的样式表语言。它与 HTML 一起&#xff0c;帮助开发者对网页进行美化和布局。CSS通过定义网页元素的颜色、字体、大小、背景、边框等属性&#xff0c;使网页变得更加美…

华清远见嵌入式学习——网络编程——小项目

项目要求&#xff1a; 代码实现&#xff1a; 服务器端&#xff1a; #include <myhead.h>//定义协议包 struct proto {char type;char name[20];char text[128]; };int main(int argc, const char *argv[]) {//判断从终端输入的字符串的个数if(argc ! 3){printf("…

【算法萌新闯力扣】:卡牌分组

力扣热题&#xff1a;卡牌分组 一、开篇 今天是备战蓝桥杯的第22天。这道题触及到我好几个知识盲区&#xff0c;以前欠下的债这道题一并补齐&#xff0c;哈希表的遍历、最大公约数与最小公倍数&#xff0c;如果你还没掌握&#xff0c;这道题练起来&#xff01; 二、题目链接:…

别再被面试官问倒了!快速失败与安全失败的区别详解

大家好&#xff0c;我是小米&#xff0c;一个热爱技术分享的程序员大哥哥。今天&#xff0c;我们来聊一个在Java面试中经常会被问到的问题——"快速失败"&#xff08;fail-fast&#xff09;和"安全失败"&#xff08;fail-safe&#xff09;的区别。这两个概…

45岁后,3部位“越干净”,往往身体越健康,占一个也要恭喜!

众所周知&#xff0c;人的生命有长有短&#xff0c;而我们的身体健康状态&#xff0c;也同样会受到年龄的影响&#xff0c;就身体的年龄层次而言&#xff0c;往往需要我们用身体内部的干净程度来维持&#xff0c;换句话说就是&#xff1a;若是你的身体内部越干净&#xff0c;那…

【接口技术】实验2:基本I/O实验

实验2 基本I/O实验 一、实验目的 1&#xff1a;掌握I/O端口地址译码电路的工作原理。 2&#xff1a;掌握简单并行接口的工作原理及使用方法。 二、实验内容 1&#xff1a;I/O端口地址译码实验 I/O地址译码电路不仅与地址信号有关&#xff0c;而且与控制信号有关。参加译码…

【面试HOT200】滑动窗口篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于【CodeTopHot200】进行的&#xff0c;每个知识点的修正和深入主要参…

Flink实战(11)-Exactly-Once语义之两阶段提交

0 大纲 [Apache Flink]2017年12月发布的1.4.0版本开始&#xff0c;为流计算引入里程碑特性&#xff1a;TwoPhaseCommitSinkFunction。它提取了两阶段提交协议的通用逻辑&#xff0c;使得通过Flink来构建端到端的Exactly-Once程序成为可能。同时支持&#xff1a; 数据源&#…