文章目录

  • 前言
  • 一、什么是守护进程?
  • 二、会话和进程组
    • 会话
    • 进程组
  • 三、守护进程的编程流程
  • 四、命令行指令让进程守护化
  • 总结
  • 总结

前言

这节课我来给大家讲解在Linux下如何让进程守护化,运行在后台,处理我们的任务.


正文开始!

一、什么是守护进程?

守护进程也称为精灵进程(Daemon),是运行在后台的一种特殊进程.它独立于控制中断并且周期性的执行某种任务或者等待处理某些发生的事件.

Linux系统启动是会启动很多服务清楚,这些系统服务进程没有控制终端,不能直接和用户交互.其他进程都是在用户登录或运行程序时创建.在运行结束或者用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直运行这.这种进程都有一个名称叫守护进程(Daemon).

例如:udevd负责维护/dev目录下的设备文件,acpid负责电源管理,syslogd负责维护/var/log下的日志文件,可以看出守护进程通常采用以d结尾的名称,表示Daemon.

一般以服务器的方式工作,对外提供服务的服务器,都是以守护进程(精灵进程)的方式在服务器中工作的,一旦启动之后,除非用户主动关闭.否则,一直会运行.

二、会话和进程组

会话

每打开一个控制中断,或者在用户登录时,系统就会创建新会话.
每个会话通常都与一个控制中断相关联.
在该会话中允许的第一个进程称作会话首进程,通常这个首进程就是shell.

下面是与会话相关的系统调用:
代表创建新会话,当前调用进程不能是组长进程

举个栗子:

进程组

每个进程都属于某个进程组,进程组是由一个或多个相互间有关联的进程组成的,他的目的是为了进行作业控制.
进程租的主要特征就是信号可以发给进程组中的所有进程:这个信号可以使同一个进程组中的所有进程终止,停止或者继续运行.
每个进程组都由进程组id唯一标识,并且有一个组长进程.进程组id就是组长进程的pid.只要在某个进程组中还有一个进程存在,则该进程组就存在.
即使组长进程终止了,在#include pid_t setsid(void);创建新会话,当前调用进程不能是组长进程pid_t getsid(pid_t isd);获取进程的所属会话id,pid=0是代表当前进程进程组依然存在.

三、守护进程的编程流程

必须要调用一个函数setsid():将调用进程设置称为独立的会话
这个函数要求进程组的组长不能调用.

我如何不成为组长呢?
你可以称为进程组内的第二个进程!—>常规做法,fork()子进程,子进程就不再是组长进城啦,他就可以成功调用setsid();

必须要做的:
if(fork()>0) exit(0);
setsid();

选做的内容:
之前我们在学习管道的时候,写端一直在写,读端关闭,写端会被终止,被信号SIGPIPE;

server一直向已经关闭的client写入,server也会收到SIGPIPE.

忽略SIGPIPE信号!

更改进程的工作目录,如何更改进程的工作目录呢?—>chdir

一般必做的:

  1. close(0,1,2)[很少有人这样做]

    Linux下的"信息黑洞或者垃圾桶",就是向这个文件里面读写的内容都被丢弃了!

  2. 打开/dev/null,并且进行对0,1,2进行重定向.

代码如下
dameonize.hpp

#pragma once
#include <cstdio>
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void daemonize()
{// 1.忽略SIGPIPE信号signal(SIGPIPE, SIG_IGN);// 2.更改进程的工作目录// chdir();// 3.让自己不要成为进程组组长if (fork() > 0)exit(0);// 4.设置自己是一个独立的会话setsid();// 5.重定向0,1,2int fd = 0;if (fd = open("dev/null", O_RDWR) != -1){dup2(fd, STDIN_FILENO);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);// 6.关闭掉不需要的fdif(fd>STDERR_FILENO){close(fd);}}
}

serverTcpd.cc

#include "util.hpp"
#include "log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include"daemonize.hpp"
using namespace std;class ServerTcp;
struct ThreadData
{ThreadData(int sock, string clientIp, uint16_t clientPort, ServerTcp *ts): _sock(sock), _clientIp(clientIp), _clientPort(clientPort), _this(ts){}int _sock;string _clientIp;uint16_t _clientPort;ServerTcp *_this;
};
//大小写转化
// TCP && UDP支持全双工
void transService(int sock, const string &clientIp, uint16_t clientPort)
{assert(sock > 0);assert(!clientIp.empty());assert(clientPort > 1024);char inbuffer[BUFFER_SIZE];while (true){ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为读到的都是字符串if (s > 0){// read successinbuffer[s] = '\0';if (strcasecmp(inbuffer, "quit") == 0){logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);break;}logMessage(DEBUG, "trans before: %s[%d]>> %s", clientIp.c_str(), clientPort, inbuffer);//可以进行大小写转化了for (int i = 0; i < s; i++){if (isalpha(inbuffer[i]) && islower(inbuffer[i]))inbuffer[i] = toupper(inbuffer[i]);}write(sock, inbuffer, sizeof(inbuffer));logMessage(DEBUG, "trans after: %s[%d]>> %s", clientIp.c_str(), clientPort, inbuffer);}else if (s == 0){// pipe:读端一直在读,写端不写了,并且关闭了写端,读端会如何?--->s==0,代表对端关闭// s==0,代表对方关闭,Client退出logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);break;}else{logMessage(DEBUG, "%s[%d] -- read: %s", clientIp.c_str(), clientPort, strerror(errno));break;}}//只要走到这里,一定是client退出了,服务到此结束close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄露!logMessage(DEBUG, "server close %d done", sock);
}void execCommand(int sock, const string &clientIp, uint16_t clientPort)
{assert(sock > 0);assert(!clientIp.empty());assert(clientPort > 1024);char command[BUFFER_SIZE];while (true){ssize_t s = read(sock, command, sizeof(command) - 1); //我们认为读到的都是字符串if (s > 0){command[s]='\0';logMessage(DEBUG,"[%s:%d] exec [%s]",clientIp.c_str(),clientPort,command);std::string safe;if((safe.find("rm")!=std::string::npos)||(safe.find("unlink")!=std::string::npos)){break;}// 我们是以r方式打开的文件,没有写入//所以我们无法通过dup2的方式得到对应的结果FILE* fp=popen(command,"r");if(fp==nullptr){logMessage(WARINING,"exec %s failed, because: %s",command,strerror(errno));break;}char line[1024];while(fgets(line,sizeof(line)-1,fp)!=nullptr){write(sock,line,strlen(line));}// dup2(sock,fp->_fileno);// fflush(fp);pclose(fp);logMessage(DEBUG,"[%s]:%d exec [%s]... done",clientIp.c_str(),clientPort,command);}else if (s == 0){// pipe:读端一直在读,写端不写了,并且关闭了写端,读端会如何?--->s==0,代表对端关闭// s==0,代表对方关闭,Client退出logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);break;}else{logMessage(DEBUG, "%s[%d] -- read: %s", clientIp.c_str(), clientPort, strerror(errno));break;}}//只要走到这里,一定是client退出了,服务到此结束close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄露!logMessage(DEBUG, "server close %d done", sock);
}class ServerTcp
{public:ServerTcp(uint16_t port, string ip = ""): _listenSock(-1), _port(port), _ip(ip), _tp(nullptr){}~ServerTcp(){}public:void init(){// 1.创建socket_listenSock = socket(AF_INET, SOCK_STREAM, 0);if (_listenSock < 0){logMessage(FATAL, "socket:%s", strerror(errno));exit(SOCKET_ERR);}logMessage(DEBUG, "socket:&s,%d", strerror(errno), _listenSock);// 2.bind绑定// 2.1填充服务器struct sockaddr_in local; //用户栈memset(&local, 0, sizeof local);local.sin_family = AF_INET;local.sin_port = htons(_port);_ip.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(_ip.c_str(), &local.sin_addr));// 2.2本地socket信息,写入_sock对应的内核区域if (bind(_listenSock, (const sockaddr *)&local, sizeof local) < 0){logMessage(FATAL, "bind: %s", strerror(errno));exit(BIND_ERR);}logMessage(DEBUG, "bind: %s", strerror(errno));// 3.监听socket,为何要监听呢?tcp是面向连接的!if (listen(_listenSock, 5 /*后面再说*/) < 0){logMessage(FATAL, "listen: %s", strerror(errno));exit(LISTEN_ERR);}logMessage(DEBUG, "listen: %s", strerror(errno));//允许别人来连接你了// 4.加载线程池_tp = ThreadPool<Task>::getInstance();}void loop(){// signal(SIGCHLD,SIG_IGN);//只在Linux下有效_tp->start();logMessage(DEBUG, "thread pool start success,thread num: %d", _tp->ThreadNum());while (true){struct sockaddr_in peer;socklen_t len = sizeof(peer);// 4.获取连接,accept的返回值是一个新的socket fd??// 4.1 _listenScok:监听&&获取新的连接--->sock// 4.2 serviceSock:给用户提供新的socket服务int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);if (serviceSock < 0){//获取连接失败logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);continue;}// 4.1获取客户端基本信息uint16_t peerPort = ntohs(peer.sin_port);string peerIp = inet_ntoa(peer.sin_addr);logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",strerror(errno), peerIp.c_str(), peerPort, serviceSock);// 5.提供服务,小写转大写// // 5.0 v0版本----单进程--一点进行transService,主执行流就无法进行向后执行,只能提供完毕服务后才能进行accept// transService(serviceSock, peerIp, peerPort);// //5.1 V1---多进程版本---父进程打开的文件会被子进程继承!// pid_t id=fork();// assert(id!=-1);// if(id==0)// {//     close(_listenSock);//     //子进程//     transService(serviceSock, peerIp, peerPort);//     exit(0);// }// //父进程// close(serviceSock);//这一步是一定要做的!// //waitpid();默认是阻塞等待!WNOHANG// //方案1// // //5.1 V1.1---多进程版本// //爷爷进程// pid_t id=fork();// if(id==0)// {//     //爸爸进程//     close(_listenSock);//     //又进行了一次fork//     if(fork>0) exit(0);//     //孙子进程--就没有爸爸进程了--孤儿进程--被系统领养了--回收问题就交给了系统来回收//     transService(serviceSock, peerIp, peerPort);//     exit(0);// }// close(serviceSock);// //爸爸进程直接终止,立马得到退出码,释放僵尸进程状态// pid_t ret=waitpid(id,nullptr,0);//就用阻塞式等待// (void)ret;// //5.2 v2版本---多线程// //这里不需要关闭文件描述符了!// ThreadData* td=new ThreadData(serviceSock,peerIp,peerPort,this);// pthread_t tid;// pthread_create(&tid,nullptr,startRountine,(void*)td); 5.3 v3版本 --- 线程池版本  5.3.1 构建任务  5.3 v3.1// Task t(serviceSock, peerIp, peerPort, std::bind(&ServerTcp::transService, this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));// _tp->push(t); 5.3 v3.2// Task t(serviceSock, peerIp, peerPort, transService);// _tp->push(t);5.3 v3.3Task t(serviceSock, peerIp, peerPort, execCommand);_tp->push(t);// logMessage(DEBUG,"server provide service start ...");// sleep(1);}}// static void *startRountine(void *args)// {//     pthread_detach(pthread_self());//     ThreadData *td = static_cast<ThreadData *>(args);//     td->_this->transService(td->_sock, td->_clientIp, td->_clientPort);//     delete (td);// }// TCP && UDP支持全双工// void transService(int sock, const string &clientIp, uint16_t clientPort)// {//     assert(sock > 0);//     assert(!clientIp.empty());//     assert(clientPort > 1024);//     char inbuffer[BUFFER_SIZE];//     while (true)//     {//         ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为读到的都是字符串//         if (s > 0)//         {//             // read success//             inbuffer[s] = '\0';//             if (strcasecmp(inbuffer, "quit") == 0)//             {//                 logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);//                 break;//             }//             logMessage(DEBUG, "trans before: %s[%d]>> %s", clientIp.c_str(), clientPort, inbuffer);//             //可以进行大小写转化了//             for (int i = 0; i < s; i++)//             {//                 if (isalpha(inbuffer[i]) && islower(inbuffer[i]))//                     inbuffer[i] = toupper(inbuffer[i]);//             }//             write(sock, inbuffer, sizeof(inbuffer));//             logMessage(DEBUG, "trans after: %s[%d]>> %s", clientIp.c_str(), clientPort, inbuffer);//         }//         else if (s == 0)//         {//             // pipe:读端一直在读,写端不写了,并且关闭了写端,读端会如何?--->s==0,代表对端关闭//             // s==0,代表对方关闭,Client退出//             logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);//             break;//         }//         else//         {//             logMessage(DEBUG, "%s[%d] -- read: %s", clientIp.c_str(), clientPort, strerror(errno));//             break;//         }//     }//     //只要走到这里,一定是client退出了,服务到此结束//     close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄露!//     logMessage(DEBUG, "server close %d done", sock);// }
private:int _listenSock;uint16_t _port;string _ip;//引入线程池ThreadPool<Task> *_tp;
};
static void Usage(string proc)
{cerr << "Usage\n\t" << proc << " port ip" << endl;cerr << "Example\n\t" << proc << " 8080  127.0.0.1\n"<< endl;
}// ./serverTcp local_port [local_ip]
int main(int argc, char *argv[])
{if (argc != 2 && argc != 3){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = stoi(argv[1]);string ip;if (argc == 3){ip = argv[2];}daemonize();//我们的进程就会成为守护进程ServerTcp svr(port, ip);svr.init();svr.loop();return 0;
}


此时服务器就部署在了Linux中.

注意:
进程守护化以后,只能使用kill命令杀掉该进程!

四、命令行指令让进程守护化

nohup [进程名] &

hello.cc

#include<iostream>
#include<cstdio>
#include<unistd.h>
int main()
{while(true){std::cout<<"hello rose"<<std::endl;sleep(1);}return 0;
}





重新登录用户之后

这个进程已经成为了话首了!!!

这样就让进程守护化啦!!!

总结

进程守护化的三种方法

  1. 自己写(严重推荐)
  2. 系统调用daemon()函数
  3. nohup ./command &,默认形成日志; nohup.out

总结

(本章完!)

[Linux]----守护进程相关推荐

  1. Linux守护进程实现

    Linux守护进程 redis版: void daemonize(void) {int fd;if (fork() != 0) exit(0); /* parent exits */setsid(); ...

  2. Linux 命令详解(六)Linux 守护进程的启动方法

    Linux 守护进程的启动方法 http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html

  3. .NET跨平台实践:.NetCore、.Net5/6 Linux守护进程设计

    几年前,我写过两篇关于用C#开发Linux守护进程的技术文章,分别是<.NET跨平台实践:用C#开发Linux守护进程.NET跨平台实践:再谈用C#开发Linux守护进程 - 完整篇 这就是本文 ...

  4. .NET跨平台实践:再谈用C#开发Linux守护进程 — 完整篇

    Linux守护进程是Linux的后台服务进程,相当于Windows服务,对于为Linux开发服务程序的朋友来说,Linux守护进程相关技术是必不可少的,因为这个技术不仅仅是为了开发守护进程,还可以拓展 ...

  5. .NET跨平台实践:用C#开发Linux守护进程

    Linux守护进程(Daemon)是Linux的后台服务进程,它脱离了与控制终端的关联,直接由Linux init进程管理其生命周期,即使你关闭了控制台,daemon也能在后台正常工作. 一句话,为L ...

  6. 深入理解Linux守护进程

    深入理解Linux守护进程Linux服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户.提供这些服务的程序是由运行在后台的守护进程(daem ...

  7. Linux守护进程的创建(结合nginx框架)

    Linux守护进程的创建(结合nginx框架) 先介绍几个相关函数: int dup2(arg1,arg2):参数一指向的内容赋给参数二,shi的参数二也能访问参数一所指向的内容,并返回新的描述符 i ...

  8. linux+守护进程+php,【转载】Linux 守护进程的编程方法

    [转载]Linux 守护进程的编程方法 原文见: http://www.linuxdevelop.org/tingxx/show.php?table=c&id=3 Linux 守护进程的编程方 ...

  9. linux 守护进程_网络工程师之linux守护进程

    Linux守护进程就是通常所说的DEAMON进程,linux后台服务多种多样,每一个服务都运行一个对应的程序,这些后台程序对应的进程就是守护进程.系统中可以看到很多如DHCPD和HTTPD之类的进程, ...

  10. 【Linux】Linux 守护进程的启动方法

    转载:Linux 守护进程的启动方法 "守护进程"(daemon)就是一直在后台运行的进程(daemon). 本文介绍如何将一个 Web 应用,启动为守护进程. 一.问题的由来 W ...

最新文章

  1. ORACLE导入导出后发生中文乱码的原因及解决办法
  2. Oracle表无法expdp,{Oracle数据库}EXPDP报错ORA-39171、ORA-01691解决方法
  3. 你所不知道的端口号知识!
  4. Apache Camel 3 –骆驼核心vs骆驼核心引擎(较小的核心)
  5. ubuntu cd 改变路径
  6. 关联查询mysql_《MySQL数据库》关联查询
  7. colgroup标签
  8. 1.7 理解 Dropout
  9. 手写模拟器,将电子文档转换为手写字体,就用这个软件
  10. 时间序列分析实验报告总结_时间序列分析实验报告
  11. 部署LAMP平台,构建企业web服务器
  12. 洛谷 P1919 模板】A*B Problem升级版(FFT快速傅里叶)
  13. 轻快步伐远不足以跟上轻快心情
  14. warning C4183: ‘Cricle‘: member function definition looks like a ctor, but name does not match enclo
  15. dbeaver(下载、安装图文过程)
  16. proftpd 服务器配置
  17. iOS访问 self-signed(自签名) HTTPS
  18. mysql point 经纬度_lbs - Mysql POINT类型数据,怎么计算经纬度偏差
  19. 黑苹果 技嘉 B250M-DS3H-CF i57500 HD630 EFI引导驱动发布
  20. ubuntu11.10 华为无线上网卡e303s

热门文章

  1. css精灵图(雪碧图)切图
  2. 中软国际python机试题_中软国际入职机试题.doc
  3. lenovo L480 进入bios_NVIDIA显卡刷BIOS教程,秒变超频显卡,将显卡性能发挥到极致!...
  4. 蓝桥杯嵌入式第七届省赛——“模拟液位检测告警系统”旧板标准库
  5. 解决阿里云OSS,打开图片地址无法预览,直接下载
  6. gradle android面试,(十七)Gradle面试问题
  7. 硬盘格式化恢复技巧分享
  8. 微信思维导图(通讯录)
  9. 京东亿级商品搜索排序规则技术全面公开
  10. Qt随手笔记(五)vs+qt使用QAxObject读取word(内容、句子、段落、表格)