前言
这节课我来给大家讲解在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
一般必做的:
-
close(0,1,2)[很少有人这样做]
Linux下的"信息黑洞或者垃圾桶",就是向这个文件里面读写的内容都被丢弃了! -
打开/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,2
int fd = 0;
if (fd = open("dev/null", O_RDWR) != -1)
{
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
// 6.关闭掉不需要的fd
if(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 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);
}
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.3
Task 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;
}
重新登录用户之后
这个进程已经成为了话首了!!!
这样就让进程守护化啦!!!
总结
进程守护化的三种方法
- 自己写(严重推荐)
- 系统调用daemon()函数
- nohup ./command &,默认形成日志; nohup.out
总结
(本章完!)
转载:https://blog.csdn.net/m0_61560468/article/details/128519109