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

文章目录

  • 前言
  • 一. 日志介绍
  • 二. 简单日志
    • 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文件的输入输出数据丢进这个黑洞文件中。

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

结束语

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JDK、JRE、JVM的特点和关联

Java 的三个重要的概念是 JDK&#xff08;Java Development Kit&#xff09;、JRE&#xff08;Java Runtime Environment&#xff09;和 JVM&#xff08;Java Virtual Machine&#xff09;。它们之间有着密切的关联&#xff0c;同时又有不同的职责和特点。 JDK&#xff08;Java…

中伟视界:创新解决方案,搭建自适应的AI算法模型训练平台

搭建AI算法模型自训练平台是当今人工智能领域的热门话题&#xff0c;但是其中存在着许多技术难点需要克服。 自训练平台需要具备高效的算法模型&#xff0c;这就要求能够处理庞大的数据量并进行高速计算。 平台需要具备强大的数据管理及存储能力&#xff0c;以满足训练过程中的…

C#,《小白学程序》第二十六课:大数乘法(BigInteger Multiply)的Toom-Cook 3算法及源程序

凑数的&#xff0c;仅供参考。 1 文本格式 /// <summary> /// 《小白学程序》第二十六课&#xff1a;大数&#xff08;BigInteger&#xff09;的Toom-Cook 3乘法 /// Toom-Cook 3-Way Multiplication /// </summary> /// <param name"a"></par…

C语言进阶之笔试题详解(1)

引言&#xff1a; 对指针知识进行简单的回顾&#xff0c;然后再完成笔试题。 ✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&#xff1a;《C语言进阶》 &#x1f388;跟着猪巴戒&#xff0c;一起学习C语言&#x1f388; 目录 引言&#xff1a; 知识简单回顾 指针是什么 指针变…

基于51单片机的公交自动报站系统

**单片机设计介绍&#xff0c; 基于51单片机的公交自动报站系统 文章目录 一 概要公交自动报站系统概述工作原理应用与优势 二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 很高兴为您介绍基于51单片机的公交自动报站系统&#xff1a; 公交自动报…

[网鼎杯 2020 朱雀组]Nmap

启动环境 结合题目首先就是要知道关于关于nmap命令 相关的命令-oN 标准保存 -oX XML保存 -oG Grep保存 -oA 保存到所有格式 -iL 读取文件内容&#xff0c;以文件内容作为搜索目标 -o 输出到文件 -sP Ping 扫描 还有许多 nmap命令https://blog.csdn.net/weixin_735627…

【知网稳定检索】第九届社会科学与经济发展国际学术会议 (ICSSED 2024)

第九届社会科学与经济发展国际学术会议 (ICSSED 2024) 2024 9th International Conference on Social Sciences and Economic Development 第九届社会科学与经济发展国际学术会议(ICSSED 2024)定于2024年3月22-24日在中国北京隆重举行。会议主要围绕社会科学与经济发展等研究…

java io 流,输入流和输出流;节点流和处理流;字节流和字符流

文章目录 java 中 IO 流分为几种?按照流的流向分&#xff0c;可以分为输入流和输出流&#xff1b;按照流的角色划分为节点流和处理流。IO流主要的分类方式有以下3种&#xff1a; java中的IO流也是工作中使用到比较频繁的一个内容&#xff0c;今天以这篇文章来了解它的概念和整…

快速认识Linux的几个指令

我们先简单认识几个指令&#xff0c;为之后的指令学习打好基础 打开XShell并登录云服务器 01.pwd指令 pwd命令的作用是显示当前在Linux系统中所处的路径 02.ls指令 ls命令的作业是罗列出当前路径下的文件名&#xff08;即pwd的路径下&#xff09;&#xff0c;由于我们没有新…

2023.11.23使用flask实现在指定路径生成文件夹操作

2023.11.23使用flask实现在指定路径生成文件夹操作 程序比较简单&#xff0c;实现功能&#xff1a; 1、前端输入文件夹 2、后端在指定路径生成文件夹 3、前端反馈文件夹生成状态 main.py from flask import Flask, request, render_template import osapp Flask(__name__)a…

WorkPlus即时通讯软件,以自主安全为底座,连接工作的一切

在当今竞争激烈的商业环境中&#xff0c;中大型企业对于移动办公平台的需求越来越迫切。在众多可选的平台中&#xff0c;WorkPlus凭借其高性价比和针对中大型企业的特色功能&#xff0c;成为了许多企业的首选。本文将为各位读者深度解析WorkPlus私有化部署的优势&#xff0c;带…

Co-DETR:DETRs与协同混合分配训练代码学习笔记

关于论文的学习笔记&#xff1a;Co-DETR:DETRs与协同混合分配训练论文学习笔记-CSDN博客 作者提出了一种新的协同混合任务训练方案&#xff0c;即Co-DETR&#xff0c;以从多种标签分配方式中学习更高效的基于detr的检测器。这种新的训练方案通过训练ATSS和Faster RCNN等一对多标…

Proteus仿真--基于PG12864LCD设计的指针式电子钟

本文介绍基于PG12864LCD设计的指针式电子钟&#xff08;完整仿真源文件及代码见文末链接&#xff09; 仿真图如下 本设计中时间芯片选用DS1302芯片&#xff0c;液晶选用PG12864LCD模块&#xff0c;按键K1-K3&#xff0c;K1用于时分选择&#xff0c;K2用于调整功能&#xff0c…

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

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

Docker容器化部署若依微服务ruoyi-cloud项目

系统环境 接下来的内容以 Ubuntu 22.04.1 操作系统为例。 下载安装Docker Ubuntu hihi-IdeaCentre-GeekPro-15ICK:~$ sudo su [sudo] hi 的密码&#xff1a; roothi-IdeaCentre-GeekPro-15ICK:/home/hi# docker ps 找不到命令 “docker”&#xff0c;但可以通过以下软件包安…

C# 使用NPOI操作Excel的工具类

写在前面 NPOI是POI项目的.NET迁移版本。POI是一个开源的Java 读写 Excel、Word 等微软Ole2组件文档的项目&#xff1b;使用NPOI可以在没有安装Office或者相应环境的机器上对Word或Excel文档进行读写操作。 NPOI类库中操作EXCEL有两个模块分别是&#xff1a; 1️.HSSF模块&a…

Java每日一题:26. 删除有序数组中的重复项

删除有序数组中的重复项 分析&#xff1a; 数组是有序的&#xff0c;因此重复的元素会相邻 每次取出两个数进行比较&#xff0c;因此&#xff0c;需要有两个变量去存储每次取出的值 采用双指针方法&#xff1a; 指针p和q&#xff0c; pnum1&#xff0c;qnum2 p和q进行比较&…

一些在使用Python中常用网页字符串处理方法

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 首先一些Python字符串处理的简易常用的用法。 1.去掉重复空格 s "hello hello hello" s .join(s.split())2.去掉所有回车&#xff08;或其他字…

可验证随机函数(VRF)

文章目录 一、背景以及场景共识发展第一代 POW “以力取胜”第二代 POS/DPOS “民主投票”第三代 VRF “运气抽签” 二、可验证随机函数&#xff08;VRF&#xff09;快速开始1. VRF是什么?2. MD5 hash函数和VRF&#xff08;Verifiable Random Function&#xff09;区别3. VRF-…

Java核心知识点整理大全15-笔记

Java核心知识点整理大全-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全2-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全3-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全4-笔记-CSDN博客 Java核心知识点整理大全5-笔记-CSDN博客 Java核心知识点整理大全6…