【Linux网络编程六】服务器守护进程化Daemon
- 一.背景知识:前台与后台
- 二.相关操作
- 三.Linux的进程间关系
- 四.自成会话
- 五.守护进程四步骤
- 六.服务器守护进程化
一.背景知识:前台与后台
核心知识就是一个用户在启动Linux时,都会给一个session会话,这个会话里会存在一个前台进程,和多个后台进程。
二.相关操作
三.Linux的进程间关系
Linux中的进程之间的关系,不仅仅是相互独立的,还可以过程进程组,进程组的组长就是第一个进程的pid。
四.自成会话
当一个任务以后台进程在会话中执行,然后我们将会话关闭,重新启动一个会话,将会发现这个任务虽然还存在,但其实已经不再是原先的那个任务了,因为它的父进程bash已经退出,它被系统自动领养了。(它原来的会话是会被记录下来的)
所以进程组是会收到用户的登录和退出的影响。进程组就代表着一个任务,也就是任务是会收到用户的退出影响,用户一旦退出,那么该任务就不再属于你了,也就是该任务已经没有了。你把会话都关闭了,那么里面的所有任务都会不存在了。
这里我想说的就是我们想让一个任务一直执行,不受用户的登录和退出影响,就必须使用守护进程!
守护进程的核心就是自成会话。
因为创建新会话的进程不能是进程组里的组长,所以我们就直接让当前进程创建子进程,然后再让当前进程直接退出,让子进程创建会话。
五.守护进程四步骤
守护进程四步骤:①忽略其他信号②自成会话③更改工作目录④重定向标准输入与输出。
因为自成会话后,新会话就不再与终端有联系了,就不需要标准输出和标准输入和标准错误了。
而打印日志可以直接往文件里输入。
其实系统中提供了守护进程化的接口调用:
六.服务器守护进程化
其实守护进程的本质就是后台进程,服务器一旦启动了守护进程化,那么就不会受用户的退出影响,就会一直在运行。
//守护进程
#pragma once
#include <signal.h>
#include <unistd.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string nullfile="/dev/null";
void Daemon(const std::string &cwd="")
{
//第一步忽略其他异常信号,防止被信号杀死
signal(SIGCLD,SIG_IGN);
signal(SIGPIPE,SIG_IGN);
signal(SIGSTOP,SIG_IGN);
//第二步将自己变成新的会话,不受其他会话管理
if(fork()>0)exit(0);
//让孙子进程来创建新的会话,因为自成组长的进程不能创建新会话
setsid();//让使用该函数的进程创建新的会话,并属于该会话
//第三步更改当前进程的路径
//根据需要传入不同的路径就更改到不同路径下
if(!cwd.empty())
{
chdir(cwd.c_str());
}
//第四步,将标准输入,标准输出,标志错误,重定向到垃圾桶文件里,新的会话不再与终端关联
int fd=open(nullfile.c_str(),O_RDWR);
if(fd>0)//打开成功
{
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
close(fd);
}
}
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include "Log.hpp"
#include "TASK.hpp"
#include "ThreadPool.hpp"
#include "Daemon.hpp"
Log lg;
const std::string defaultip="0.0.0.0";
const int defaultfd=-1;
int backlog=10;//一般不要设置太大
enum
{
SockError=2,
BindError,
AcceptError,
};
class Tcpserver;
class ThreadData
{
public:
ThreadData(int &fd,const std::string& ip,uint16_t &port,Tcpserver* svr):_sockfd(fd),_ip(ip),_port(port),_svr(svr)
{}
public:
int _sockfd;
std::string _ip;
uint16_t _port;
Tcpserver* _svr;
};
class Tcpserver
{
public:
Tcpserver(const uint16_t &port,const std::string &ip=defaultip):_listensock(-1),_port(port),_ip(ip)
{}
void Init()
{
//服务器端启动之前创建套接字,绑定。
//一开始的这个套接字是属于监听套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
lg(Fatal,"sock create errno:%d errstring:%s",errno,strerror(errno));
exit(SockError);
}
//创建套接字成功
lg(Info,"sock create sucess listensock:%d",_listensock);
int opt = 1;
setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法进行立即重启(tcp协议的时候再说)
//创建成功后就要绑定服务器的网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
//填充信息
local.sin_family=AF_INET;
local.sin_port=htons(_port);
inet_aton(_ip.c_str(),&local.sin_addr);
//填充完毕,真正绑定
if((bind(_listensock,(struct sockaddr*)&local,sizeof(local)))<0)
{
lg(Fatal,"bind errno:%d errstring:%s",errno,strerror(errno));
exit(BindError);
}
lg(Info,"bind socket success listensock:%d",_listensock);//绑定成功
//udp中绑定成功后就可以进行通信了,但tcp与udp不同。tcp是面向连接的,在通信之前
//需要先获取新连接,获取到新连接才能进行通信。没有获取连接那么就要等待连接,等待新连接的过程叫做监听,监听有没有新连接。
//需要将套接字设置成监听状态
listen(_listensock,backlog);//用来监听,等待新连接,只有具备监听状态才能识别到连
}
static void* Routine(void *args)//静态成员函数无法使用成员函数,再封装一个服务器对象
{
//子线程要和主线程分离,主线程不需要等待子线程,直接回去重新获取新连接
pthread_detach(pthread_self());
ThreadData* td=static_cast<ThreadData*>(args);
//子线程用来服务客户端
td->_svr->Service(td->_sockfd,td->_ip,td->_port);
delete td;
return nullptr;
}
void Run()
{
Daemon();
signal(SIGPIPE,SIG_IGN);//防止服务端往一个已经关闭的文件描述符里写入,忽略带操作系统发送的信号
//一启动服务器,就将线程池中的线程创建
ThreadPool<TASK>::GetInstance()->Start();//单例对象
//静态函数,通过类域就可以使用
lg(Info,"tcpserver is running");
while(true)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//将套接字设置成监听状态后,就可以获取新连接
int sockfd=accept(_listensock,(struct sockaddr*)&client,&len);
//获取从监听套接字那里监听到的连接。然后返回一个新套接字,通过这个套接字与连接直接通信,而监听套接字继续去监听。
if(sockfd<0)
{
lg(Fatal,"accept error,errno: %d, errstring: %s",errno,strerror(errno));
exit(AcceptError);
}
//获取新连接成功
//将客户端端网络信息带出来
uint16_t clientport=ntohs(client.sin_port);
char clientip[32];
inet_ntop(AF_INET,&client.sin_addr,clientip,sizeof(clientip));
//根据新连接进行通信
lg(Info,"get a new link...sockfd: %d,clientip: %s,clientport: %d",sockfd,clientip,clientport);
//构建任务
TASK t(sockfd,clientip,clientport);
//将任务放进线程池里,线程就会到线程池里去执行任务。
ThreadPool<TASK>::GetInstance()->Push(t);
}
}
void Service(int &sockfd,const std::string &clientip,uint16_t &clientport)
{
char inbuffer[1024];
while(true)
{
ssize_t n=read(sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<"client say# "<<inbuffer<<std::endl;
//加工处理一下
std::string echo_string="tcpserver加工处理数据:";
echo_string+=inbuffer;
//将加工处理的数据发送会去
write(sockfd,echo_string.c_str(),echo_string.size());
}
else if(n==0)//如果没有用户连接了,那么就会读到0.服务器端也就不要再读了
{
lg(Info,"%s:%d quit, server close sockfd: %d",clientip.c_str(),clientport,sockfd);
break;
}
else
{
lg(Fatal,"read errno: %d, errstring: %s",errno,strerror(errno));
}
}
}
~Tcpserver()
{}
private:
int _listensock;//监听套接字只有一个,监听套接字用来不断获取新的连接。返回新的套接字
std::string _ip;
uint16_t _port;
};
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
void Usage(std::string proc)
{
std::cout<<"\n\rUsage: "<<proc<<" port[1024+]\n"<<std::endl;
}
//./tcpclient ip port
int main(int args,char* argv[])
{
if(args!=3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip=argv[1];
uint16_t serverport = std::stoi(argv[2]);
struct sockaddr_in server;
socklen_t len=sizeof(server);
server.sin_family=AF_INET;
server.sin_port=htons(serverport);
inet_pton(AF_INET,serverip.c_str(),&server.sin_addr);
while(true)
{
//创建套接字
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
std::cout<<"create sockfd err "<<std::endl;
}
//创建套接字成功,创建完套接字后该干什么?
//连接服务器端的套接字,所以客户端用户需要知道服务器端的网络信息的
int cnt=10;
bool isreconnect=false;
do
{
int n=connect(sockfd,(struct sockaddr*)&server,len);
if(n<0)//服务器关闭了,肯定会连接失败
{
isreconnect=true;
cnt--;
std::cout<<"connect sock err...,cnt: "<<cnt<<std::endl;
sleep(12);
}
else//重连成功了
{
break;
}
}while(cnt&&isreconnect);
//连接成功
//连接成功后,就可以直接通信了,就可以直接给对方写消息了。
if(cnt==0)
{
std::cerr<<"user offline.."<<std::endl;
break;//用户直接不玩了
}
std::string message;
std::cout<<"Please enter#";
getline(std::cin,message);
//往套接字里写
int n=write(sockfd,message.c_str(),message.size());
if(n<0)//服务器端会将该套接字关闭,然后就写不进去了。需要重新创建套接字连接
{
std::cerr<<"write error..."<<std::endl;
continue;
}
char outbuffer[1024];
//接收服务器发送的加工处理消息
n=read(sockfd,outbuffer,sizeof(outbuffer));
if(n>0)
{
outbuffer[n]=0;
std::cout<<outbuffer<<std::endl;
}
close(sockfd);
}
return 0;
}