守护进程是在后台运行不受终端控制的进程(如输入、输出等),一般的网络服务都是以守护进程的方式运行。守护进程脱离终端的主要原因有两点:(1)用来启动守护进程的终端在启动守护进程之后,需要执行其他任务。(2)(如其他用户登录该终端后,以前的守护进程的错误信息不应出现)由终端上的一些键所产生的信号(如中断信号),不应对以前从该终端上启动的任何守护进程造成影响。要注意守护进程与后台运行程序(即加&启动的程序)的区别。
创建守护进程的过程:
1. 调用fork创建子进程。父进程终止,让子进程在后台继续执行。
2. 子进程调用setsid产生新会话期并失去控制终端调用setsid()使子进程进程成为新会话组长和新的进程组长,同时失去控制终端。
3. 忽略SIGHUP信号。会话组长进程终止会向其他进程发该信号,造成其他进程终止。
4. 调用fork再创建子进程。子进程终止,子子进程继续执行,由于子子进程不再是会话组长,从而禁止进程重新打开控制终端。
5. 改变当前工作目录为根目录。一般将工作目录改变到根目录,这样进程的启动目录也可以被卸掉。
6. 关闭打开的文件描述符,打开一个空设备,并复制到标准输出和标准错误上。 避免调用的一些库函数依然向屏幕输出信息。
7. 重设文件创建掩码清除从父进程那里继承来的文件创建掩码,设为0。
8. 用openlog函数建立与syslogd的连接。
创建守护进程的例子如下程序所示:
void daemon_init(const char* pname,int facility) { int i; pid_t pid; struct rlimit rl; struct sigaction sa; /* 清除文件模式创建掩码,使新文件的权限位不受原先文件模式创建掩码的权限位的影响*/ umask(0); if(getrlimit(RLIMIT_NOFILE,&rl) < 0) { perror("getrlimit() error"); exit(-1); } if((pid = fork()) < 0) { perror("fork() error"); exit(-1); } else if(pid > 0) /*父进程终止 */ exit(0); setsid(); /* 子进程成为会话首进程*/ sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if(sigaction(SIGHUP,&sa,NULL) < 0) { perror("sigaction() error"); exit(-1); } if((pid = fork()) < 0) { perror("fork() error"); exit(-1); } else if(pid > 0) exit(0); /* 第一个子程进终止,保证后面操作不会分配终端 */ if(chdir("/")<0) /* 改变工作目录 */ { perror("chdir() error"); exit(-1); } if(rl.rlim_max == RLIM_INFINITY) rl.rlim_max = 1024; for(i=0;i<rl.rlim_max;++i) /*关闭所有打开的文件描述字*/ close(i); openlog(pname, LOG_PID, facility); /*用syslogd处理错误*/ }
现在要用守护进程实现一个时间服务器,呈现的功能是:服务器运行后自动成为守护进程,返回shell;客户端运行后收到服务器发来的当前时间。
时间服务器程序(timeserver.c)如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <errno.h> 6 #include <sys/types.h> 7 #include <fcntl.h> 8 #include <signal.h> 9 #include <syslog.h> 10 #include <sys/resource.h> 11 #include <sys/socket.h> 12 #include <netinet/in.h> 13 #include <arpa/inet.h> 14 #include <time.h> 15 16 #define MAXLINE 100 17 18 void daemon_init(const char* pname,int facility) 19 { 20 int i; 21 pid_t pid; 22 struct rlimit rl; 23 struct sigaction sa; 24 /* 清除文件模式创建掩码,使新文件的权限位不受原先文件模式创建掩码的权限位的影响*/ 25 umask(0); 26 if(getrlimit(RLIMIT_NOFILE,&rl) < 0) 27 { 28 perror("getrlimit() error"); 29 exit(-1); 30 } 31 if((pid = fork()) < 0) 32 { 33 perror("fork() error"); 34 exit(-1); 35 } 36 else if(pid > 0) /*父进程终止 */ 37 exit(0); 38 setsid(); /* 子进程成为会话首进程*/ 39 sa.sa_handler = SIG_IGN; 40 sigemptyset(&sa.sa_mask); 41 sa.sa_flags = 0; 42 if(sigaction(SIGHUP,&sa,NULL) < 0) 43 { 44 perror("sigaction() error"); 45 exit(-1); 46 } 47 if((pid = fork()) < 0) 48 { 49 perror("fork() error"); 50 exit(-1); 51 } 52 else if(pid > 0) 53 exit(0); /* 第一个子程进终止,保证后面操作不会分配终端 */ 54 if(chdir("/")<0) /* 改变工作目录 */ 55 { 56 perror("chdir() error"); 57 exit(-1); 58 } 59 if(rl.rlim_max == RLIM_INFINITY) 60 rl.rlim_max = 1024; 61 for(i=0;i<rl.rlim_max;++i) /*关闭所有打开的文件描述字*/ 62 close(i); 63 openlog(pname, LOG_PID, facility); /*用syslogd处理错误*/ 64 } 65 66 int main(int argc,char *argv[]) 67 { 68 int listenfd, connfd; 69 socklen_t addrlen, len; 70 struct sockaddr cliaddr; 71 struct sockaddr_in server; 72 char buff[MAXLINE]; 73 time_t ticks; 74 int n; 75 bzero(&server, sizeof(server)); 76 bzero(&cliaddr,sizeof(cliaddr)); 77 server.sin_family = AF_INET; 78 server.sin_port = htons(5050); 79 server.sin_addr.s_addr = htonl(INADDR_ANY); 80 daemon_init(argv[0], 0); 81 if((listenfd=socket(AF_INET, SOCK_STREAM, 0))==-1) 82 { 83 syslog(LOG_NOTICE|LOG_LOCAL0,"socket error"); 84 exit(-1); 85 } 86 if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr))==-1) 87 { 88 syslog(LOG_NOTICE|LOG_LOCAL0,"socket error"); 89 exit(-1); 90 } 91 if(listen(listenfd,5)==-1) 92 { 93 syslog(LOG_NOTICE|LOG_LOCAL0,"listen error"); 94 exit(-1); 95 } 96 for ( ; ; ) 97 { 98 len = sizeof(cliaddr); 99 connfd = accept(listenfd,&cliaddr, &len); 100 ticks = time(NULL); 101 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); 102 if((n= write(connfd, buff, strlen(buff)))==-1) 103 syslog(LOG_NOTICE|LOG_LOCAL0,"write error"); 104 close(connfd); 105 } 106 }
客户端程序(timeclient.c)如下:
1 #include <unistd.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <netdb.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #define PORT 5050 9 #define MAXDATASIZE 100 10 11 int main(int argc, char *argv[]) 12 { 13 int fd, numbytes; 14 char buf[MAXDATASIZE]; 15 struct sockaddr_in server; 16 if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 17 { 18 perror("Create socket failed."); 19 exit(-1); 20 } 21 bzero(&server, sizeof(server)); 22 server.sin_family = AF_INET; 23 server.sin_port = htons(PORT); 24 server.sin_addr.s_addr = htonl(INADDR_ANY); 25 if (connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) 26 { 27 perror("connect failed."); 28 exit(-1); 29 } 30 if( ((numbytes = recv(fd, buf, MAXDATASIZE, 0)) == -1)) 31 { 32 perror("recv error."); 33 exit(-1); 34 } 35 buf[numbytes] ='\0'; 36 printf("Server Message: %s\n",buf); 37 close(fd); 38 }
程序运行过程: 先运行时间服务器程序,再在运行客户端程序。
运行时间服务器程序结果如下:
此时timeserver是个守护进程,已经在运行,通过ps -axj命令可以查看该进程,查看结果如下: