Unix环境高级编程(十四)守护进程实现时间服务器
守护进程是在后台运行不受终端控制的进程(如输入、输出等),一般的网络服务都是以守护进程的方式运行。守护进程脱离终端的主要原因有两点:(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命令可以查看该进程,查看结果如下: