因为守护进程没有控制终端,所以当有事发生时它们得有输出消息的某种方法可用,而这些消息既可能是普通的通告性消息,也可能是需由系统管理员处理的紧急事件消息。syslog函数是输出这些消息的标准方法,它把这些消息发送给syslogd守护进程。
备注:遇到类似的函数,具体说明请查看APUE
#include <syslog.h> void syslog(int priority, const char *message,...); void openlog(const char *ident, int options, int facility); void closelog(void);
服务器程序daytimetcpsrv.c:
#include <stdio.h> #include <netdb.h> #include <sys/socket.h> #include <time.h> #include <syslog.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> extern int errno; int daemon_proc; #define MAXLINE 1024 #define MAXFD 64 int daemon_init(const char *pname, int facility); int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp); int main(int argc, char **argv) { int listenfd, connfd; socklen_t len; char buff[MAXLINE]; time_t ticks; struct sockaddr_in cliaddr; daemon_init(argv[0], 0); listenfd = tcp_listen(argv[1], argv[2], NULL); for (; ;){ len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len); inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)); strcat(buff, ".this is a test\n"); syslog(LOG_INFO, buff); ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); write(connfd, buff, strlen(buff)); close(connfd); } } int daemon_init(const char *pname, int facility) { int i; pid_t pid; if ((pid = fork()) < 0) return -1; else if (pid) _exit(0); if (setsid() < 0) return -1; signal(SIGHUP, SIG_IGN); if ((pid = fork()) < 0) return -1; else if (pid) _exit(0); daemon_proc = 1; chdir("/"); for (i = 0; i < MAXFD; i++) close(i); open("/dev/null", O_RDONLY); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); openlog(pname, LOG_PID, facility); } int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp) { int listenfd, n; const int on = 1; struct addrinfo hints, *res, *ressave; bzero(&hints, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((n = getaddrinfo(host, serv, &hints, &res)) != 0){ printf("tcp_listen error for %s,%s:%s\n", host, serv, gai_strerror(n)); exit(1); } ressave = res; do{ listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (listenfd < 0) continue; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0) break; close(listenfd); } while ((res = res->ai_next) != NULL); if (res == NULL) printf("tcp_listen error for %s,%s\n", host, serv); listen(listenfd, 5); if (addrlenp) *addrlenp = res->ai_addrlen; freeaddrinfo(ressave); return listenfd; }
客户端程序daytimetcpcli.c:
#include <stdio.h> #include <netdb.h> #include <sys/socket.h> #define MAXLINE 1024 int tcp_connect(const char *host, const char *serv); int main(int argc, char **argv) { int sockfd, n; char recvline[MAXLINE + 1]; socklen_t len; struct sockaddr_in cliaddr; if (argc != 3){ printf("argument should be 3\n"); exit(1); } sockfd = tcp_connect(argv[1], argv[2]); len = sizeof(cliaddr); getpeername(sockfd, (struct sockaddr *)&cliaddr, len); inet_ntop(AF_INET, &cliaddr.sin_addr, recvline, sizeof(recvline)); printf("connect to %s\n", recvline); while ((n = read(sockfd, recvline, MAXLINE)) > 0){ recvline[n] = 0; fputs(recvline, stdout); } exit(0); } int tcp_connect(const char *host, const char *serv) { int sockfd, n; struct addrinfo hints, *res, *ressave; struct sockaddr_in *cliaddr; bzero(&hints, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((n = getaddrinfo(host, serv, &hints, &res)) != 0){ printf("tcp_connect error for %s,%s:%s\n", host, serv, gai_strerror(n)); exit(1); } 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; cliaddr = (struct sockaddr_in *)res->ai_addr; close(sockfd); } while ((res = res->ai_next) != NULL); if (res == NULL) printf("tcp_connect error for %s,%s\n", host, serv); freeaddrinfo(ressave); return sockfd; }
服务端:
leichaojian@ThinkPad-T430i:~$ ./daytimetcpsrv ThinkPad-T430i 9878客户端:
leichaojian@ThinkPad-T430i:~$ ./daytimetcpcli ThinkPad-T430i 9878 connect to 0.0.0.0 Wed Oct 8 21:07:35 2014
Oct 8 21:07:35 ThinkPad-T430i ./daytimetcpsrv[10528]: 127.0.0.1.this is a test
用于产生子进程
setsid用于创建一个新的回话。当前进程变为新会话的会话头进程以及新进程组的进程组头进程,从而不再有控制终端。
忽 略SIGHUP信号并再次调用fork。该函数返回时,父进程实际上是上一次调用fork产生的子进程,它被终止掉,留下新的子进程继续运行。再次 fork的目的是确保本守护进程将来即使打开了一个终端设备,也不会自动获得控制终端。当没有控制终端的一个会话头进程打开一个终端设备时(该终端不会是 当前某个其他会话的控制终端),该终端自动成为这个会话头进程的控制终端。然而再次调用fork之后,我们确保新的子进程不再是一个会话头进程,从而不能 自动获得一个控制终端。这里必须霍略SIGHUP信号,因为当会话头进程(即首次fork产生的子进程)终止时,其会话中的所有进程(即再次fork产生 的子进程)都收到SIGHUP信号。
因 为之前关闭了所有的描述符,所以要打开这三个基本描述符并且重定向,让read返回0,write系统调用丢弃所写的数据(书上说如果调用了syslog 函数,则不要调用类似printf之类的函数,因为会被简单的忽略掉)。因为如果继续关闭,则万一有新的进程打开一个描述符,却占用了0,1,2这三个描 述符,则可能导致将错误的数据发送给客户端。
旧的服务器只是等待客户请求的到达,如FTP,Telnet,TFTP等。这些进程都是在系统自举阶段从/etc/rc文件中启动,而且每个进程执行几乎 相同的启动任务:创建一个套接字,把本服务器的众所周知端口捆绑到该套接字,等待一个连接或一个数据报,然后派生子进程。子进程为客户提供服务,父进程则 继续等待下一个客户请求。这个模型存在两个问题:
(1)所有这些守护进程含有几乎相同的启动代码,既表现在创建套接字上,也表现在演变成守护进程上(类似我们的daemon_init函数)
(2)每个守护进程在进程表中占据一个表项,然而它们大部分时间处于睡眠状态。
而新版本的系统通过提供inetd守护进程(因特网超级服务器)来简化问题:
(1)通过inetd处理普通守护进程的大部分启动细节来简化守护进程的编写。这么一来每个服务器不再有调用daemon_init函数的必要。
(2)单个进程就能为多个服务等待外来的客户请求,以此取代每个服务一个进程的做法。这么做减少了系统中的进程总数。
字段 | 说明 |
service_name | 必须在/etc/services文件中定义 |
socket_type | stream(对于tcp)或dgram(对于udp) |
protocol | 必须在/etc/protocols文件中定义:tcp或udp |
wait-falg | 对于TCP一半为nowait,对于UDP一般为wait |
login-name | 来自/etc/passwd的用户名,一般为root |
server-program | 调用exec指定的完整路径名 |
server-program-arguments | 调用exec指定的命令行参数 |
下面是xinetd.conf文件中的若干行:
ftp | stream | tcp | nowait | root | /usr/bin/ftpd | ftpd -l |
telnet | stream | tcp | nowait | root | /usr/bin/telnetd | telnetd |
在启动阶段,读入/etc/xinetd.conf文件并给该文件中指定的每个服务创建一个适当类型(字节流或数据报)的套接字。inetd能够处理的服 务器的最大数目取决于inetd能够创建的描述符的最大数目。新创建的每个套接字都被加入到将由某个select调用使用的一个描述符集中。
为每个套接字调用bind,指定捆绑相应服务器的众所周知端口和通配地址。这个TCP或UDP端口号通过调用getservbyname获得,作为函数参数的是相应服务器在配置文件中的service-name字段和protocol字段。
对于每个TCP套接字,调用listen以接收外来的连接请求。对于数据报套接字则不执行本步骤
创建完毕所有套接字之后,调用select等待其中任何一个套接字变为可读。TCP监听套接字将在有一个新连接准备好可被接受时变为可读,UDP套接字将 在有一个数据报到达时变为可读。inetd的不部分时间花在阻塞于select调用内部,等待某个套接字变为可读。
当select返回指出某个套接字已可读之后,如果该套接字是一个TCP套接字,而且其服务器的wait-flag值为nowait,那就调用accept接受这个新连接。
inetd守护进程调用fork派生进程,并由子进程处理服务请求。子进程关闭要处理的套接字描述符之外的所有描述符:对于TCP服务器来说,这个套接字 是由accept返回的新的已连接套接字,对于UDP服务器来说,这个套接字是父进程最初创建的UDP套接字。子进程dup2三次,把这个待处理套接字的 描述符复制到描述符0,1和2,然后关闭原套接字描述符(由accept返回的已连接的TCP套接字)。
子进程然后调用exec执行由相应的server-program字段指定的程序来具体处理请求,相应的server-program-arguments字段值则作为命令行参数传递给该程序。
如果第五步中的select返回的是一个字节流套接字,那么父进程必须关闭已连接套接字(就像标准并发服务器那样)。父进程再次调用select,等待下 一个变为可读的套接字。(因为TCP设置的nowait,意味着inetd不必等待某个子进程终止就可以接收对于该子进程所提供之服务的另一个连接。如果 对于某个子进程所提供之服务的另一个连接确实在该子进程终止之前到达:accept返回,那么父进程再次调用select:意味着要关闭已连接的套接字, 继续执行步骤4,5,6)
给一个数据报服务指定wait标志导致父进程执行的步骤发生变化。这个标志要求inet必须在这个套接字再次称为slect调用的候选套接字之前等待当前服务该套接字的子进程终止。发生的变化有以下几点:
[1]fork返回到父进程时,父进程保存子进程的进程ID。这么做使得父进程能够通过查看由waitpid返回的值确定这个子进程的终止时间
[2]父进程通过使用FD_CLR宏关闭这个套接字在select所用描述符集中对应的位,达成在将来的select调用中禁止这个套接字的目的。这点意味着子进程将接管该套接字,直到自身终止为止。
[3]当子进程终止时,父进程被通知一个SIGCHLD信号,而父进程的信号处理函数将取得这个子进程的进程ID。父进程通过打开相应的套接字在select所用描述符集中对应的位,使得该套接字重新成为select的候选套接字。
2)inetd守护进程的服务器程序
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <signal.h> #include <fcntl.h> #include <unistd.h> #include <time.h> #include <netinet/in.h> #define MAXLINE 1024 int main(int argc, char **argv) { socklen_t len; struct sockaddr_in cliaddr; char buff[MAXLINE]; time_t ticks; openlog(argv[0], 0); len = sizeof(cliaddr); getpeername(0, (struct sockaddr *)&cliaddr, &len); inet_ntop(AF_INET, (struct sockaddr *)&cliaddr.sin_addr, buff, sizeof(buff)); printf("connect from %s\n", buff); ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); write(0, buff, strlen(buff)); close(0); exit(0); }
mydaytime 9999/tcp
mydaytime stream tcp nowait leichaojian /home/leichaojian/newdaytimetcpserv3 newdaytimetcpserv3
leichaojian@ThinkPad-T430i:~$ ./newdaytimetcpserv3 connect from 0.0.0.0 Fri Oct 3 14:27:31 2014