网络编程套接字(4):日志和守护进程

网络编程套接字(4): 日志和守护进程

5. 增加日志功能

接着上篇的内容,增加日志功能可以更好地,更高效地帮助我们发现在网络通信中遇到的问题

日志分为5个等级

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

log.hpp

#include<iostream>
#include<string>
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<cstdarg>
#include<time.h>
using namespace std;

// 日志系统是有等级的

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

static 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 "Fatal";
    default:
        return "Uknown";
    }
}

string getTime()
{
    time_t curr=time(nullptr);
    struct tm*tmp=localtime(&curr);
    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);
    return buffer;
}

// 日志格式: 日志等级 时间 pid 消息体
// logLeft:  日志等级 时间 pid        logRight:  消息体
// logMessage(Debug, "hello: %d, %s",12,s.c_str())   Debug, hello: 12, world

void logMessage(int level, const char*format,...)
{
    char logLeft[1024];
    string level_string=toLevelString(level);
    string curr_time=getTime();
    snprintf(logLeft,sizeof(logLeft),"[%s] [%s] [%d] ",level_string.c_str(), 	                      curr_time.c_str(),getpid());

    char logRight[1024];
    va_list p;
    va_start(p,format);
    vsnprintf(logRight,sizeof(logRight),format,p);
    va_end(p);

    printf("%s%s\n",logLeft,logRight);
}

关于给服务端添加日志打印我这里就不给出了,放到我的码云:lesson35/log_v1/tcpServer.hpp · 遇健/Linux - 码云 - 开源中国 (gitee.com)

添加成功后运行结果:

在这里插入图片描述

6. 守护进程

6.1 进程知识补充

在这里插入图片描述

(1) 进程组
  • 进程组就是一个或多个进程的集合,每个进程除了有一个PID外,还属于一个进程组。

  • 每一个进程组,都有一个唯一的标识PGID,属于同一个进程组的进程其PGID相同。

  • 进程组中的第一个进程作为组长进程,将其PID作为进程组的PGID

如下,我们同时启动了3个后台进程,它们属于同一进程组,进程组中的第一个进程PID=14378的进程作为组长进程

在这里插入图片描述

(2) 任务

启动一个进程就是启动一个任务

  • 前台任务:通过终端启动,并且在启动后一直占据终端

  • 后台任务:启动时与终端无关,或者通过终端启动后转入后台运行(即释放终端),不影响用户继续在终端中工作

在我们每次登录XShell后,bash会默认占据前台任务,也就是命令行解释器shell(即占用终端的控制权)

当把进程任务自动切换为前台任务时,shell自动切换为后台任务,我们输入的命令就无效了

任务管理命令(Shell中控制进程组的方式):

  • jobs:查看所有任务

在这里插入图片描述

  • fg:把任务提到前台

此时这个任务就会变成前台任务,shell自动切换为后台任务,命令行解释器失效

在这里插入图片描述

  • ctrl+z:暂停前台任务

在这里插入图片描述

  • bg:让暂停的任务在后台继续运行

在这里插入图片描述

(3) 会话

Linux是多用户多任务的分时系统,所以必须要支持多个用户同时使用一个操作系统。当一个用户登录一次系统就形成一次会话。在一个会话中,用户可以与系统进行交互,执行命令、操作文件、启动程序等。

比如,我们先启动3个后台进程,在启动3个前台进程,后获取这些进程的信息

在这里插入图片描述

  • 这些进程分别属于2个进程组,它们与同一个终端关联,属于同一个会话

在这里插入图片描述

  • 启动进程就是启动任务,在每一次登录系统会为我们创建一次会话,会话里至少有bash任务进行命令行解释

  • 命令行里可以启动多个任务,每个任务最终以进程组的形式在会话里存在。

  • 所以一个会话里可能存在很多进程组

  • 大小概念:会话 >= 进程组 >= 进程

6.2 守护进程

(1) 概念

上面的知识概念总结:

  • 进程组分为:前台任务和后台任务

  • 如果把后台任务提到前台,则老的前台任务无法运行

  • 在会话中只能有一个前台任务在运行,所以当我们在命令行启动一个进程的时候,bash就无法运行了

  • 如果登录就是创建一个会话,bash任务启动我们的进程就是在当前会话中创建新的前后台任务,那么我们如果退出呢?就会销毁会话,可能会影响会话内部的所有任务!

我们之前的服务器都是这样的:

每一个服务端进程组与bash进程组同属于一个会话,再次登录Xshell启动服务端就会创建新的会话

在这里插入图片描述

可是一般的网络服务器,为了不受到用户的登录注销的影响,就必须让服务端自成进程组,自成会话,使其与终端的状态无关,可以一直运行的进程,这样的进程就称作守护进程

在这里插入图片描述

(2) 创建守护进程

我们用的setsid函数自己实现守护进程,不使用linux自带生成守护进程的接口daemon

在这里插入图片描述

creates a session and sets the process group ID: 创建会话并设置进程组ID

这个函数的使用关键:调用的进程不能是组长进程

守护进程的创建步骤:

  1. 让调用进程忽略掉异常信号
  2. fork()创建子进程,让父进程直接退出,自己不再是组长进程
  3. 调用setsid,新建会话, 子进程自成进程组,成为会话的首进程
  4. 将标准输入、输出和错误重定向到/dev/null中
  5. 调用close关闭文件描述符,防止守护进程与终端或其他进程的关联
  6. 调用setsid,新建会话, 自己成为会话的首进程

dev/null是linux下的特殊文件,会对写入的内容进行丢弃,通常被用作丢弃不需要的输出或测试程序在遇到写入错误时的行为。

在这里插入图片描述

daemon.hpp

#pragma once

#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"

// 1. setsid();
// 2. setsid(), 调用进程,不能是组长! 我们怎么保证自己不是组长呢?
// 3. 守护进程, 忽略异常信号 b. 0, 1, 2要特殊处理 c. 进程的工作路径可能要更改

// 守护进程的本质: 是孤儿进程的一种
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, error string: %s",errno,                                  strerror(errno));
        exit(SET_ERR);
    }

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

    // 5. 处理后续对于0,1,2的问题  --- /dev/null 文件就像垃圾桶
    int fd=open("/dev/null",O_RDWR);  
    if(fd<0)
    {
        logMessage(Fatal,"open error, code: %d, error string: %s",errno,strerror(errno));
        exit(SET_ERR);
    }

    dup2(fd,0);
    dup2(fd,1);
    dup2(fd,2);
    close(fd);
}

给服务端加上该代码

在这里插入图片描述

运行结果:

在这里插入图片描述

  • ?表示该进程与终端无关,我们已经让此服务端以守护进程的方式运行

  • 该进程的PPID为1,说明OS领养了守护进程,守护进程本质是孤儿进程的一种

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值