原理:
当关闭服务器时,客户端还能正常运行。
守护进程的特点?
(1)和终端无关。
(2)和输入(键盘)输出(屏幕)无关。
(3)和具体的文件路径无关,实际上它的位置位于根目录。
如何创建守护进程?
(1)摆脱终端
创建一个新的子进程,然后将父亲进程杀死。
通过setsid,创建新的会话,并成为会话的首领。(另立山头)
目的是为了避免自己又再开启一个终端,还要再创建一个新进程,并将父亲进程杀死。
(2)关掉输入输出
将输入输出的描述符跟黑洞绑定。
(3)改变文件路径
(4)设置文件的权限掩码
//服务器守护进程
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <mqueue.h> //消息队列
#include <signal.h>
#include <semaphore.h>
#include <sys/socket.h> //套接字接口
#include <arpa/inet.h> //网络地址的转换
#include <time.h>
/*创建守护进程*/
void be_daemon(void)
{
if(fork()>0)
{
//退出父亲进程
exit(0);
}
//这个下面就是子进程干的事
//创建一个新会话,并成为新会话的首领
setsid();
//为了避免会话首领重启终端,那么再创建一个子进程
if(fork()>0)
{
exit(0);
}
//这个下面就是子进程的子进程干的事
chdir("/");//将路径改成根目录
int i;
for(i=0;i<3;i++)
{
close(i); //关掉输入,输出,错误显示端
}
//将三个描述符和黑洞绑定
///dev/null这个文件是一个特殊文件,写东西呢进去会被全部吸收,
//读又读不出来
open("/dev/null",O_RDWR);//将0这个描述符和null文件绑定
open("/dev/null",O_RDWR); //将1这个描述符和null文件绑定
open("/dev/null",O_RDWR); //将2这个描述符和null文件绑定
//设置文件权限的掩码为0,让进程里的文件默认权限为0777
umask(0);
}
//发送当前时间
void sendtime(int fd)
{
char buffer[1024];
time_t ticks;
ticks=time(NULL);//获取从1970年1月1日0时到现在的时间间隔
snprintf(buffer,sizeof(char)*1024,"%.24s\n",ctime(&ticks));
write(fd,buffer,strlen(buffer));
}
void sigproc(int signo)
{
int state;
//处理子进程
while(waitpid(-1,&state,0)>0)
{
printf("child process has been processed!\n");
}
}
int main(int argc,char *argv[])
{
//注册一个函数,处理结束了的子进程
signal(SIGCHLD,sigproc);
be_daemon();
/*1、创建一个socket*/
/*int socket(int domain, int type, int protocol);
domain是地址族,一般用ipv4的地址族,也就是AF_INET
type表示用tcp还是udp, tcp用SOCK_STREAM, UDP用SOCK_DGRAM
protocol是协议的意思,一般等于0
函数返回的是socket描述符,是一个整型值
*/
int sockfd=socket(AF_INET,SOCK_STREAM,0);
/*2、将上面的套接字描述符和ip地址绑定
sock地址的准备:
用到struct sockaddr_in结构体,然后强制转换成struct sockaddr的通用结构体
绑定:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
其中,struct sockaddr不好使用,一般用sockaddr_in来准备数据,再强制转换成sockaddr
*/
struct sockaddr_in sockin; //创建一个sockaddr_in变量
bzero(&sockin,sizeof(sockin)); //先用0填充
sockin.sin_family=AF_INET; //设置地址族
//inet_pton(AF_INET,"127.0.0.1",&sockin.sin_addr);//设置ip地址
sockin.sin_addr.s_addr=INADDR_ANY; //上句可以改成这句,表示所有网卡都能接受连接
sockin.sin_port=htons(2017); //设置端口为2017
//以上5句代码是准备sockaddr的数据
if(bind(sockfd,(struct sockaddr *)&sockin,sizeof(sockin))<0)
{
perror("bind");
exit(EXIT_FAILURE);
}
/*
3、监听客户端的连接,如有则放入队列
int listen(int sockfd, int backlog);
backlog可以理解为队列中元素的最大数量
*/
if(listen(sockfd,10)<0)
{
perror("listen");
exit(EXIT_FAILURE);
}
while(1)
{
/*4、从队列中取出一个连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr是连接的客户端地址,是一个输出型的参数
addrlen是地址结构体的大小,也是输出型参数
当然,如果不需要,可以设置为NULL
函数返回一个文件描述符,可以借助这个描述符向客户端读写数据
如果队列为空,则本函数阻塞。
*/
struct sockaddr_in clientaddr;//用来保存客户端的网络地址
socklen_t clientlen=sizeof(clientaddr);//客户端网络地址的长度
int fd=accept(sockfd,(struct sockaddr *)&clientaddr,&clientlen);
if(fd<0)
{
perror("accept");
exit(EXIT_FAILURE);
}
/*所谓的多并发,就是能够同时服务多个客户端
此处,一旦提取了一个连接,则创建一个进程去服务它.
那父亲进程就可以解放开来,继续回到上面accept下一个连接
*/
if(fork()==0)
{
//子进程进来后第一步是将复制了的sockfd关闭,
//由于有两份sockfd,关掉一个并不会真正关闭连接
close(sockfd);
char clientip[20];
printf("客户端%s(%d)已连接.\n",
inet_ntop(AF_INET,&clientaddr.sin_addr,clientip,
sizeof(clientip)),ntohs(clientaddr.sin_port));
/*5、用write向客户端发送数据*/
sendtime(fd);
close(fd);
}
}
}
//客户端
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <mqueue.h> //消息队列
#include <signal.h>
#include <semaphore.h>
#include <sys/socket.h> //套接字接口
#include <arpa/inet.h> //网络地址的转换
#include <time.h>
//argv[1]是ip地址,argv[2]是端口
int main(int argc, char *argv[])
{
if(argc<3)
{
printf("usage:filename ipaddress port");
return -1;
}
/*1, 创建套接字*/
int sockfd=socket(AF_INET,SOCK_STREAM,0);
/*2, 设置网络地址*/
struct sockaddr_in sockin;
sockin.sin_family=AF_INET;
inet_pton(AF_INET,argv[1],&sockin.sin_addr);
sockin.sin_port=htons(atoi(argv[2]));
/*3, 连接服务器
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
*/
if(connect(sockfd,(struct sockaddr *)&sockin, sizeof(sockin))<0)
{
perror("connet");
exit(EXIT_FAILURE);
}
/*4, 读取信息*/
char buffer[1024]={0};
if(read(sockfd,buffer,sizeof(buffer))<0)
{
perror("read");
}
//将信息输出到屏幕上
write(STDOUT_FILENO,buffer,strlen(buffer));
close(sockfd);
}
运行结果:
当运行服务器端时,关闭服务器,连接客户端是可以连接成功的,这样就创建了守护进程。
查看守护进程号:ps aux |grep timed
杀死守护进程:kill -9 守护进程号