【Linux】网络与守护进程

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:进程状态、类型、优先级、命令行参数概念、环境变量(重要)、程序地址空间

在这里插入图片描述


👉🏻守护进程

🌈 概念
Linux 的守护进程(Daemon Process)是一种在后台运行的特殊类型的进程,它们与用户没有交互界面,通常用于执行系统任务、服务或守护程序。

守护进程在 Linux 系统中被设计为长期运行的进程,独立于任何控制终端,并在系统启动时自动启动。它们通常在系统启动过程中由初始化进程(init process)启动,并在系统关闭时由系统管理器来终止。


而我们的网络服务,不能在bash中以前台进程的方式运行,真正的服务器,必须在Linux后台,以守护进程的方式运行

我们平常登录Linux,操作系统都会给我提供一个bash(命令行解释器),在此页面中我们也称为会话,在同一个会话中创建的进程,无非分为前台和后台进程,但是,后台进程可以有很多个,而前台进程只能有一个

同时启动的多个进程,可以是属于同一个进程组的,组ID一般是多个进程中的第一个进程
在这里插入图片描述


在这里插入图片描述
如图,我们启动一个睡眠进程,此时睡眠进程在前台,bash就退到后台去了,所以我们此时发的任何命令都无法被解析。
此时我们可以ctrl+Z终止当前进程,睡眠进程就会停止了。
在这里插入图片描述

fg 进程号数放前台,bg 进程号数放后台

👉🏻写一个守护进程

daemon函数

daemon()函数是一个Linux/Unix系统中用来创建守护进程(daemon)的函数,它通过一系列步骤将当前进程转变为一个守护进程。在标准的C库中没有daemon()函数,但是在一些系统中(如GNU libc)提供了这个函数。

以下是daemon()函数的一般形式:

int daemon(int nochdir, int noclose);
  • nochdir:如果该参数非零,表示在调用daemon()函数之后,守护进程的工作目录将不会被修改,即不会改变当前工作目录为根目录。
  • noclose:如果该参数非零,表示在调用daemon()函数之后,守护进程的标准输入、输出和错误输出将不会被关闭,即不会关闭文件描述符0、1和2。

daemon()函数的主要作用是将当前进程转变为一个守护进程。守护进程是在后台运行的系统服务,通常独立于终端会话,以提供某种服务或执行某些系统任务。通常,守护进程需要满足以下几个特性:

  1. 与终端分离:守护进程通常不应该与任何终端相关联,因此在创建守护进程时,需要将其与任何终端分离。

  2. 独立于父进程:守护进程应该是一个独立的进程,不受父进程的影响,即使父进程退出,守护进程也能够继续运行。

  3. 关闭文件描述符:通常,守护进程需要关闭与标准输入、输出和错误输出相关联的文件描述符,以确保不会受到终端会话的影响。

daemon()函数在实现这些特性时会完成一系列的操作,包括创建子进程、关闭父进程、改变工作目录、设置文件掩码等。它简化了创建守护进程的过程,使得开发者能够更容易地编写具有守护进程特性的程序。

setsid函数

setsid函数是一个Unix系统调用,用于创建一个新的会话并设置当前进程的会话ID(Session ID)为新会话的ID。它通常在守护进程(daemon)中使用,以确保进程独立于其父进程运行,并且不受终端的影响。

在调用setsid函数之后,当前进程将成为一个新的会话的领导者(session leader),同时成为一个新的进程组的领导者,并且没有控制终端。这样做有几个作用:

  1. 摆脱控制终端:会话领导者不再有控制终端,这意味着即使用户退出登录或关闭终端,该进程仍然可以继续运行。
  2. 独立于父进程:新的会话和进程组意味着进程不再受父进程的影响,即使父进程退出,进程也可以继续运行。
  3. 改变工作目录:新的会话通常会将进程的工作目录更改为根目录,以确保不受当前工作目录的影响。

setsid函数的原型如下:

pid_t setsid(void);

其中,setsid不接受任何参数,它返回一个pid_t类型的值,表示新的会话ID。如果调用成功,返回值为新的会话ID,如果失败则返回-1,并设置errno来指示错误的原因。

要使用setsid函数,通常需要在程序中先调用fork创建一个子进程,然后在子进程中调用setsid函数。这样可以确保新的会话和进程组不会受到父进程的影响。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        // 父进程退出
        exit(EXIT_SUCCESS);
    }

    // 在子进程中调用setsid函数
    pid_t sid = setsid();
    if (sid < 0) {
        perror("setsid");
        exit(EXIT_FAILURE);
    }

    // 这里是新的会话领导者

    // 关闭标准输入、输出和错误输出
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    // 守护进程的主体逻辑

    return 0;
}

以上是一个简单的守护进程的示例,其中在子进程中调用了setsid函数,创建了一个新的会话,并关闭了标准输入、输出和错误输出,最后进入守护进程的主体逻辑。


为什么要先调用fork创建一个子进程? 🤔
父进程在创建子进程后立即退出,这样子进程就不会成为孤儿进程,而是被init进程接管。这一步确保了守护进程不会有父进程,从而独立于任何终端会话。

父进程在创建子进程后立即退出,这样子进程就不会成为孤儿进程,而是被init进程接管。这一步确保了守护进程不会有父进程,从而独立于任何终端会话。

总的来说,就是如果不创建子进程,父进程成为当前会话的唯一进程组,也就是进程组的组长,父进程如果一旦退出,整个会话也就退出玩完了。

Daemon.hpp

#pragma once
#include<iostream>
#include<cstdlib>
#include<signal.h>
#include<unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

const char *root = "/";
const char *dev_null = "/dev/null";//  /dev/null: 凡是向这个目录写入的内容,自动被丢弃


bool daemon(bool ischdir, bool isclose)
{
    // 1. 忽略可能引起程序异常退出的信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // 2. 让自己不要成为组长
    if (fork() > 0)
        exit(0);

    // 3. 设置让自己成为一个新的会话, 后面的代码其实是子进程在走
    setsid();

    // 4. 每一个进程都有自己的CWD(当前目录),是否将当前进程的CWD更改成为 / 根目录
    if (ischdir)
        chdir(root);

    // 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了
    if (isclose)
    {
        close(0);
        close(1);
        close(2);
    }
    else
    {
        // 这里一般建议就用这种
        int fd = open(dev_null, O_RDWR);
        if (fd > 0)
        {
            dup2(fd, 0);//重定向,原本向标准输入中读的,现在从fd中读
            dup2(fd, 1);
            dup2(fd, 2);
            close(fd);
        }
    }
}

main.cc

#include "Daemon.hpp"
#include <unistd.h>

int main()
{
    // 变成守护进程
    Daemon(true, false);

    // 是我要执行的核心代码
    while(true)
    {
        sleep(1);
    }

    return 0;
}

👉🏻将tcp通信进程变为守护进程

代码参考:【Linux】socket编程3

代码目录:
在这里插入图片描述

Main.cc

#include <memory>

#include "TcpServer.hpp"
#include "CommErr.hpp"
#include "Translate.hpp"
#include "Daemon.hpp"

void Usage(std::string proc)
{
    std::cout << "Usage : \n\t" << proc << " local_port\n" << std::endl;
}
void Interact(int sockfd)
{
  while(true)
  {
      char buffer[1024];
    ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);//读取客户端信息
    if (n > 0)
    {
        buffer[n] = 0;
        cout<<"clinet say:"<<buffer<<endl;
        string message = buffer;
        if(write(sockfd, message.c_str(), message.size())<0)//发送回客户端
            cout<<"send fail"<<endl;
    }
    else if (n == 0) // read如果返回值是0,表示读到了文件结尾(对端关闭了连接!)
    {
        lg.LogMessage(Info, "client quit...\n");
        break;
    }
    else
    {
        lg.LogMessage(Error, "read socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
        break;
    }
  }
}
void Ping(int sockfd, InetAddrtoLocal addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "ping", sockfd);
    // 一直进行IO
    std::string message = "Ping Service Start...";
     write(sockfd, message.c_str(), message.size());//先回应来自客户端的消息
    Interact(sockfd);
   
   
}

void Translate(int sockfd,InetAddrtoLocal addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "Translate", sockfd);
   std::string message = "Translate Service Start...";

     write(sockfd, message.c_str(), message.size());//先回应来自客户端的消息
     Translater trans;
      while(true)
  {
      char buffer[1024];
    ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);//读取客户端信息
    if (n > 0)
    {
        buffer[n] = 0;
        cout<<"clinet say:"<<buffer<<endl;
        string message = trans.Excute(buffer);
        if(write(sockfd, message.c_str(), message.size())<0)//发送回客户端
            cout<<"send fail"<<endl;
    }
    else if (n == 0) // read如果返回值是0,表示读到了文件结尾(对端关闭了连接!)
    {
        lg.LogMessage(Info, "client quit...\n");
        break;
    }
    else
    {
        lg.LogMessage(Error, "read socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
        break;
    }
  }
}

// 改成大写,字符串改成大写
void Transform(int sockfd, InetAddrtoLocal addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "Transform", sockfd);
    
}
// ./tcp_server 8888
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return Usage_Err;
    }
    uint16_t port = stoi(argv[1]);

    //进程变为守护进程
    Daemon(false, false);
    lg.Enable(ClassFile);//使日志可以向显示屏打印



    std::unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port);
   
    //在内核中注册服务类型
    tsvr->RegisterFunc("ping", Ping);
    tsvr->RegisterFunc("translate", Translate);
    tsvr->RegisterFunc("transform", Transform);
    
    //初始化和启动服务端
    tsvr->Init();
    tsvr->Start();

    return 0;
}
  • /dev/null: 凡是向这个目录写入的内容,自动被丢弃
  • kill -9 PID 删除进程
  • daemon函数:将进程->守护进程化

如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值