守护进程和inetd超级服务器
守护进程(daemon)是在后台运行且不与任何终端关联的进程。守护进程通常没有控制终端,为了避免与作业控制、终端会话管理等发生不期望的任何交互,也为了避免非预期的输出到终端。
因为守护进程没有控制终端,因此需要专门的方法来输出守护进程产生的消息。syslog
函数是输出这些消息的标准方法,它把这些消息发送给syslogd
守护进程。
syslogd守护进程
Unix系统中的syslog通常随着系统的初始化脚本而启动,而且在系统运行期间一直运行。源自Berkeley的syslogd实现在启动时通常会执行以下操作:
- 读取配置文件(/etc/syslog.conf),指定本进程如何处理各种日志消息,如写到控制台或者转发到另一台主机上的syslogd进程;
- 创建Unix域套接字,给它捆绑路径名/var/run/log(在某些系统上是/dev/log);
- 创建UDP套接字,绑定514端口;
- 打开路径名/dev/klog。来自内核的任何出错消息都会作为这个设备的输入。
此后syslogd守护进程在一个无限循环中运行:调用select等待2,3,4步描述符之一变得可读,读入日志消息,并按照配置文件进行处理。如果守护进程收到SIGHUP
信号,那就重新读取配置文件。
syslog 函数
#include <syslog.h>
void syslog(int priority, const char *message, ...);
2
syslog是守护进程登记消息的常用技巧。
priority
是级别(level)
和设施(facility)
两者的结合。级别默认为LOG_NOTICE
。设施默认为LOG_USER
。
日志消息级别
日志消息设施
messsage
参数类似于printf
的格式串,不过增设了%m
规范,它将被替换成与当前error
值对应的出错消息。message参数的末尾可以出现一个换行符,不过并非必须。
当syslog被首次调用时,它创建一个UNIX域套接字,然后调用connect函数连接到syslogd守护进程创建的域套接字的众所周知路径名(譬如/var/run/log)。这个套接字一直打开,直到进程终止为止。进程也可以通过openlog
和closelog
来代替。
#include <syslog.h>
void openlog(const char *ident, int options, int family);
void closelog(void);
2
3
openlog可以在首次调用syslog前调用,closelog可以在应用进程不再发送日志消息时调用。ident
通常是程序名,是syslog加在每条日志消息之前的字符串。options
参数由下表所示的一个或多个常值的逻辑或构成。
options | 说明 |
---|---|
LOG_CONS | 若无法发送到syslogd守护进程则登记到控制台 |
LOG_NDELAY | 不延迟打开,立即创建套接字。默认在首次调用syslog时创建 |
LOG_PERROR | 既发送到syslogd,又登记到标准错误输出 |
LOG_PID | 随着每个日志消息登记进程ID |
守护进程例子
unp.h
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <syslog.h>
#include <fcntl.h>
#define MAX_BUFF_SIZE 1024
#define LISTENQ 5
#define MAX_FD_SIZE 64
int is_daemon_proc;
void err_msg(const char *, va_list);
void err_printf(const char *, ...);
void err_quit(const char *, ...);
struct addrinfo *host_serv(const char *, const char *, int, int);
int tcp_connect(const char *, const char *);
int tcp_listen(const char *, const char *, socklen_t *);
int udp_client(const char *, const char *, struct sockaddr **, socklen_t *);
int udp_connect(const char *, const char *);
int udp_server(const char *, const char *, socklen_t *);
int daemon_init(const char *, int);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
daemon.c
#include "unp.h"
int daemon_init(const char *pname, int facility)
{
int i;
pid_t pid;
/* 退出父进程,留下子进程继续运行 */
if ((pid = fork()) < 0){
return -1;
}
else if (pid){
printf("parent process exited\n");
exit(0);
}
/* 创建一个新的session。当前进程称为新会话的会话头进程以及新进程组的进程组头进程,从而不再有控制终端 */
if (setsid() < 0)
return -1;
/**
* 忽略SIGHUP并再次fork
* 再次fork的目的是确保本守护进程将来即使打开了一个终端设备,也不会自动获得控制终端。当没有控制终端的一个
* 会话头进程打开了一个终端设备时会自动成为这个会话头进程的控制终端。再次调用fork之后我们确保新的子进程不
* 再是一个会话头进程,从而不能自动获得一个控制终端。当会话头进程终止时,其会话中的所有进程都会收到SIHUP信号。
*/
signal(SIGHUP, SIG_IGN);
if ((pid = fork()) < 0)
return -1;
else if (pid)
{
printf("1st child process exited\n");
printf("Daemon process id is %d\n", pid);
exit(0);
}
is_daemon_proc = 1;
/**
* 把工作目录改到根目录下。避免出现文件系统无法卸载的问题。
*/
chdir("/");
printf("change workdir to root\n");
/**
* 关闭所有可能打开的描述符
*/
for (i = 0; i < MAX_FD_SIZE; i++)
close(i);
/**
* Presumably file descriptors 0, 1, and 2 have already been closed when this code executes,
* and there are no other threads which might be allocating new file descriptors. In this
* case, since open is required to always allocate the lowest available file descriptor
* number, these three calls to open will yield file descriptors 0, 1, and 2, unless they
* fail.
*
* https://stackoverflow.com/questions/4263173/redirecting-stdin-stdout-stderr-to-dev-null-in-c
*/
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
openlog(pname, LOG_PID, facility);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
unp.c
#include "unp.h"
void err_msg(const char *fmt, va_list ap)
{
char buff[MAX_BUFF_SIZE];
vsnprintf(buff, MAX_BUFF_SIZE, fmt, ap);
if (is_daemon_proc)
syslog(LOG_INFO, "%s", buff);
else
perror(buff);
}
void err_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_msg(fmt, ap);
va_end(ap);
}
void err_quit(const char *fmt, ...)
{
va_list ap;
char buff[MAX_BUFF_SIZE];
va_start(ap, fmt);
err_msg(fmt, ap); /* we can pass the args only to functions that
take va_args as argument. These have a v in
their name: vprintf, vfprintf, vsnprintf */
va_end(ap);
exit(0);
}
struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype)
{
struct addrinfo hints, *res;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = family;
hints.ai_socktype = socktype;
if ((getaddrinfo(host, serv, &hints, &res)) != 0)
return NULL;
return res;
}
/**
* 创建一个TCP套接字并连接到服务器
*/
int tcp_connect(const char *host, const char *serv)
{
int sockfd, n;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
{
err_quit("tcp_connect error for %s, %s: %s\n", host, serv, gai_strerror(n));
}
ressave = res;
do
{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0)
continue;
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break; /* 成功 */
close(sockfd);
} while ((res = res->ai_next) != NULL);
if (res == NULL)
err_quit("tcp_connect error for %s, %s", host, serv);
freeaddrinfo(ressave);
return sockfd;
}
/**
* 创建一个TCP套接字并捆绑服务器的众所周知的端口,而接受外来的请求
*/
int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
{
int listend, n;
const int on = 1;
struct addrinfo hints, *res, *_res;
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(host, serv, &hints, &res) != 0))
err_quit("tcp_listen error for %s, %s: %s", host, serv, gai_strerror(n));
_res = res;
do
{
listend = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (listend < 0)
continue;
setsockopt(listend, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (bind(listend, res->ai_addr, res->ai_addrlen) == 0)
break;
close(listend);
} while ((res = res->ai_next) != NULL);
if (res == NULL)
err_quit("tcp_listen error for %s, %s\n", host, serv);
listen(listend, LISTENQ); /* UDP套接字不需要调用listen,listen由TCP套接字调用,否则会报Operation not supported on socket错误 */
if (addrlenp)
*addrlenp = res->ai_addrlen;
freeaddrinfo(_res);
return listend;
}
/**
* 创建未连接的UDP套接字
*/
int udp_client(const char *host, const char *serv, struct sockaddr **saptr, socklen_t *lenp)
{
int n, sockfd;
struct addrinfo hints, *res, *_res;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("udp_client error for %s, %s: %s", host, serv, gai_strerror(n));
_res = res;
do
{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd >= 0)
break;
} while ((res = res->ai_next) != NULL);
if (res == NULL)
err_quit("udp_client error for %s, %s", host, serv);
*saptr = malloc(res->ai_addrlen);
memcpy(*saptr, res->ai_addr, res->ai_addrlen);
*lenp = res->ai_addrlen;
freeaddrinfo(_res);
return sockfd;
}
/**
* 创建已连接套接字。
* (1)本函数相比udp_client不需要结尾两个参数,调用者可以改用write代替sendto,因此本函数不必返
* 回一个套接字地址结构及其长度。
* (2)本函数几乎等同于tcp_connect,差别之一在于UDP套接字上的connect调用不会发送任何东西到对端,
* 如果存在错误(譬如对端不可达或所指定的端口上没有服务器),调用者就得等到向对端发送一个数据报
* 之后才能发现。
*/
int udp_connect(const char *host, const char *serv)
{
int sockfd, n;
struct addrinfo hints, *res, *_res;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("udp_connect error for %s, %s: %s", host, serv, gai_strerror(n));
_res = res;
do
{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1)
continue;
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break;
close(sockfd);
} while ((res = res->ai_next) != NULL);
if (res == NULL)
err_quit("udp connection error for %s, %s", host, serv);
freeaddrinfo(_res);
return sockfd;
}
/**
* 为UDP服务器创建未连接套接字
*/
int udp_server(const char *host, const char *serv, socklen_t *lenptr)
{
int sockfd, n;
struct addrinfo hints, *res, *_res;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("udp_server error for %s, %s: %s", host, serv, gai_strerror(n));
_res = res;
do
{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1)
continue;
if (bind(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break;
close(sockfd);
} while ((res = res->ai_next) != NULL);
if (res == NULL)
err_quit("udp_server error for %s, %s", host, serv);
if (lenptr)
*lenptr = res->ai_addrlen;
freeaddrinfo(_res);
return sockfd;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
TCP时间服务器守护进程
#include "unp.h"
#include <time.h>
int main(int argc, char const *argv[])
{
int listenfd, connfd;
socklen_t len, addrlen;
struct sockaddr_storage cliaddr;
char addrstr[64], buff[MAX_BUFF_SIZE];
time_t ticks;
daemon_init(argv[0], 0);
if(argc == 2)
listenfd = tcp_listen(NULL, argv[1], &addrlen);
else if(argc == 3)
listenfd = tcp_listen(argv[1], argv[2], &addrlen);
else
err_quit("usage: daytimetcpsrv [<host>] <service or port>");
len = sizeof(cliaddr);
for(;;){
connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &len);
if(connfd == -1){
continue;
}
ticks = time(NULL);
snprintf(buff, MAX_BUFF_SIZE, "%.24s\r\n", ctime(&ticks));
write(connfd, buff, strlen(buff));
close(connfd);
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
编译 Makefile
unp.o: unp.c
$(CC) -c $^
daemon.o: daemon.c
$(CC) -c $^
daytimetcpsrv.o: daytimetcpsrv.c
$(CC) -c $^
server: daytimetcpsrv.o unp.o daemon.o
$(CC) -o $@ $^
.PHONY: all clean
all: server
clean:
rm -rf *.o *.gch server
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
inetd 守护进程
4.3BSD版本通过提供一个因特网超级服务器(即inetd
守护进程)使上述问题得到简化。基于TCP或UDP的服务器都可以使用这个守护进程。它:
- 通过由inetd处理普通守护进程的大部分启动细节以简化守护进程的编写
- 单个进程就能为多个服务等待外来的客户请求,以此取代每个服务一个进程的做发,减少了系统的进程总数。
inetd读入并处理自己的配置文件(通常是/etc/inetd.conf),配置指定了本超级服务器处理哪些服务以及当一个服务请求达到时该怎么做。其工作流程如下图: