Unix网络编程代码 第13章 守护进程和inetd超级服务器
1. 概述
守护进程是在后台运行且不与任何控制终端关联的进程。unix系统通常有很多守护进程在后台运行,执行不同的管理任务。
守护进程没有控制终端通常源于它们由系统初始化脚本启动。然而守护进程也可能从某个终端由用户在shell提示符下键入命令行启动,这样的守护进程必须亲自脱离与控制终端的关联,从而避免与作业控制,终端会话管理,终端产生信号等发生任何不期望的交互,也可以避免在后台运行的守护进程非预期的输出到终端。
守护进程有多种启动方法:
1.在系统启动阶段,许多守护进程由系统初始化脚本启动。这些脚本通常位于/etc目录或以/etc/rc开头的某个目录中,它们的具体位置和内容却是实现相关的。由这些脚本启动的守护进程一开始拥有超级用户权限。
有若干个网络服务器通常从这些脚本启动:inetd超级服务器,web服务器,邮件服务器(经常是sendmail)。
2. 许多网络服务器由inetd超级服务器启动。inetd自身由上一条中的某个脚本启动。inetd监听网络请求,每当有一个请求到达时,启动相应的实际服务器(telnet服务器,FTP服务器等)
3. cron守护进程按照规则定期执行一些程序,而由它启动执行的程序同样作为守护进程运行。cron自身由第一条启动方法中的某个脚本启动
4. at命令用于指定将来某个时刻的程序执行。这些程序的执行时刻到来时,通常由cron守护进程启动执行它们,因此这些程序同样作为守护进程运行。
5.守护进程还可以从用户终端或在前台或在后台启动。这么做往往是为了测试守护进程或重启因某种原因而终止了的某个守护进程。
因为守护进程没有控制终端,所以当有事发生时它们得有输出消息的某种方法可用,而这些消息既可能是普通的通告性消息,也可能是需由系统管理员处理的紧急事件消息。syslog函数是输出这些消息的标准方法,它把这些消息发送给syslogd守护进程。
2. syslog函数,openlog函数和closelog函数
备注:遇到类似的函数,具体说明请查看APUE
- #include <syslog.h>
- void syslog(int priority, const char *message,...);
- void openlog(const char *ident, int options, int facility);
- void closelog(void);
1) 作为守护进程运行的协议无关时间获取服务器程序
服务器程序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
然后我们查看/var/log/syslog这个文件,通过查找字符串“this is a test”,发现如下的语句:
- Oct 8 21:07:35 ThinkPad-T430i ./daytimetcpsrv[10528]: 127.0.0.1.this is a test
2) 对daemon_init函数的分析
(1)fork
用于产生子进程
(2)setsid
setsid用于创建一个新的回话。当前进程变为新会话的会话头进程以及新进程组的进程组头进程,从而不再有控制终端。
(3)忽略SIGHUP信号并再次fork
忽略SIGHUP信号并再次调用fork。该函数返回时,父进程实际上是上一次调用fork产生的子进程,它被终止掉,留下新的子进程继续运行。再次fork的目的是确保本守护进程将来即使打开了一个终端设备,也不会自动获得控制终端。当没有控制终端的一个会话头进程打开一个终端设备时(该终端不会是当前某个其他会话的控制终端),该终端自动成为这个会话头进程的控制终端。然而再次调用fork之后,我们确保新的子进程不再是一个会话头进程,从而不能自动获得一个控制终端。这里必须霍略SIGHUP信号,因为当会话头进程(即首次fork产生的子进程)终止时,其会话中的所有进程(即再次fork产生的子进程)都收到SIGHUP信号。
(4)将stdin,stdout和stderr重定向到/dev/null
因为之前关闭了所有的描述符,所以要打开这三个基本描述符并且重定向,让read返回0,write系统调用丢弃所写的数据(书上说如果调用了syslog函数,则不要调用类似printf之类的函数,因为会被简单的忽略掉)。因为如果继续关闭,则万一有新的进程打开一个描述符,却占用了0,1,2这三个描述符,则可能导致将错误的数据发送给客户端。
3. inetd守护进程
旧的服务器只是等待客户请求的到达,如FTP,Telnet,TFTP等。这些进程都是在系统自举阶段从/etc/rc文件中启动,而且每个进程执行几乎相同的启动任务:创建一个套接字,把本服务器的众所周知端口捆绑到该套接字,等待一个连接或一个数据报,然后派生子进程。子进程为客户提供服务,父进程则继续等待下一个客户请求。这个模型存在两个问题:
(1)所有这些守护进程含有几乎相同的启动代码,既表现在创建套接字上,也表现在演变成守护进程上(类似我们的daemon_init函数)
(2)每个守护进程在进程表中占据一个表项,然而它们大部分时间处于睡眠状态。
而新版本的系统通过提供inetd守护进程(因特网超级服务器)来简化问题:
(1)通过inetd处理普通守护进程的大部分启动细节来简化守护进程的编写。这么一来每个服务器不再有调用daemon_init函数的必要。
(2)单个进程就能为多个服务等待外来的客户请求,以此取代每个服务一个进程的做法。这么做减少了系统中的进程总数。
1) inetd守护进程的工作流程
(0)对xinetd.conf文件的说明
字段 | 说明 |
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 |
(1)socket()
在启动阶段,读入/etc/xinetd.conf文件并给该文件中指定的每个服务创建一个适当类型(字节流或数据报)的套接字。inetd能够处理的服务器的最大数目取决于inetd能够创建的描述符的最大数目。新创建的每个套接字都被加入到将由某个select调用使用的一个描述符集中。
(2)bind()
为每个套接字调用bind,指定捆绑相应服务器的众所周知端口和通配地址。这个TCP或UDP端口号通过调用getservbyname获得,作为函数参数的是相应服务器在配置文件中的service-name字段和protocol字段。
(3)listen()
对于每个TCP套接字,调用listen以接收外来的连接请求。对于数据报套接字则不执行本步骤
(4)select()等待可读条件
创建完毕所有套接字之后,调用select等待其中任何一个套接字变为可读。TCP监听套接字将在有一个新连接准备好可被接受时变为可读,UDP套接字将在有一个数据报到达时变为可读。inetd的不部分时间花在阻塞于select调用内部,等待某个套接字变为可读。
(5)accept()
当select返回指出某个套接字已可读之后,如果该套接字是一个TCP套接字,而且其服务器的wait-flag值为nowait,那就调用accept接受这个新连接。
(6)fork()
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);
- }
在/etc/service中增加:
- mydaytime 9999/tcp
在/etc/xinetd.conf中增加:
- 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
要运行这个例子程序,
1.先要添加服务:做法,在/etc/services最后加上: mydaytime 9999/tcp
2.安装xinetd: sudo yum install xinetd
3.编辑配置:在/etc/xinetd.d/目录下新建一个mydaytime文件,内容如下:
service mydaytime
{
socket_type = stream
protocol = tcp
wait = no
user =root
server =/home/xpmo/unp/server
}
其中server是例子程序的路径
4.重启xinetdsudo killall -HUP xinetd
5.查看启动成功否:
sudo netstat -tlp
如果看到mydaytime就说明启动成功了。
安装yum install telnet
Unix网络编程代码 第13章 守护进程和inetd超级服务器的更多相关文章
- UNP学习第13章 守护进程和inetd超级服务器
Unix系统中的syslogd守护进程通常由某个系统初始化脚本启动,而且在系统工作期间一直运行. 源自Berkeley的syslogd实现在启动时执行以下步骤. (1)读取配置文件.通常为/etc/s ...
- 《Unix 网络编程》13:守护进程和 inetd 超级服务器
守护进程和 inetd 超级服务器 ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ...
- UNP学习笔记(第十三章 守护进程和inetd超级服务器)
关于守护进程可以查看apue的笔记 http://www.cnblogs.com/runnyu/p/4645046.html daemon_init函数 下面给出名为daemon_init函数,通过调 ...
- APUE读书笔记-第13章-守护进程
第13章 守护进程 13.1 引言 *守护进程也称精灵进程(daemon)是生存期较长的一种进程.它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNI ...
- 守护进程和inetd超级服务器
守护进程: 1 系统启动时,由系统初始化脚本启动.一般在/etc目录下,或者以/etc/rc开头的目录 2 许多网络服务器由inetd超级服务器启动 3 cron守护进程按规则定期执行一些程序 4 用 ...
- UNIX环境高级编程 第13章 守护进程
守护进程daemon是一种生存周期很长的进程.它们通常在系统引导时启动,在系统关闭时终止.守护进程是没有终端的,它们一直在后台运行. 守护进程的特征 在Linux系统中,可以通过命令 ps -efj ...
- 《Unix环境高级编程》读书笔记 第13章-守护进程
1. 引言 守护进程是生存期长的一种进程.它们常常在系统引导装入时启动,仅在系统关闭时才终止.它们没有控制终端,在后台运行. 本章说明守护进程结构.如何编写守护进程程序.守护进程如何报告出错情况. 2 ...
- PHP7 网络编程(二)daemon守护进程
前言 在一个多任务的计算机操作系统中,守护进程(英语:daemon,/ˈdiːmən/或/ˈdeɪmən/)是一种在后台执行的计算机程序.此类程序会被以进程的形式初始化.守护进程程序的名称通常以字母“ ...
- UNP第13章——守护进程
1. 守护进程的启动方法 (1)系统初始化脚本启动,在系统启动阶段,按照如/etc目录或/etc/rc开头的目录中的某些脚本启动,这些守护进程一开始就有超级用户权限.如inetd,cron,Web服务 ...
随机推荐
- #Leet Code# LRU Cache
语言:C++ 描述:使用单链表实现,HeadNode是key=-1,value=-1,next=NULL的结点.距离HeadNode近的结点是使用频度最小的Node. struct Node { in ...
- iOS: 学习笔记, Swift与C指针交互(译)
Swift与C指针交互 Objective-C和C API经常需要使用指针. 在设计上, Swift数据类型可以自然的与基于指针的Cocoa API一起工作, Swift自动处理几种常用的指针参数. ...
- linux c++ 遍历一个目录下的文件名 (包括子目录的文件名)
最近写代码有一个要遍历目录下的每一个文件并取得这个文件的绝对路径的需求, 我们知道linux c++中有system命令所以我在代码中 先生成了一个log,然后去读log文件的每一行文件名,然后给存储 ...
- 学习Swift -- 构造器(中)
构造器(中) 值类型的构造器代理 构造器可以通过调用其它构造器来完成实例的部分构造过程.这一过程称为构造器代理,它能减少多个构造器间的代码重复. 构造器代理的实现规则和形式在值类型和类类型中有所不同. ...
- nutch 采集效率问题
http://hi.baidu.com/jacklin/item/a8fbccf479f6a1d042c36a7c再附一篇:http://blog.csdn.net/laigood/article/d ...
- 【LA2796】Concert Hall Scheduling(最大费用最大流)
Description You are appointed director of a famous concert hall, to save it from bankruptcy. The hal ...
- 监控Activity在前后台状态的切换
public class BaseActivity extends Activity{ @Overrideprotected void onStop() { boolean isOnForegroun ...
- Apache Hadoop RPC Authentication 安全绕过漏洞
漏洞名称: Apache Hadoop RPC Authentication 安全绕过漏洞 CNNVD编号: CNNVD-201308-425 发布时间: 2013-08-28 更新时间: 2013- ...
- Apache HBase RPC身份验证中间人安全措施绕过漏洞(CVE-2013-2193)
漏洞版本: Apache Group HBase 0.94.x Apache Group HBase 0.92.x 漏洞描述: BUGTRAQ ID: 61981 CVE(CAN) ID: CVE-2 ...
- C#调用C++动态库(dll)
在实际软件开发过程中,由于公司使用了多种语言开发,在C#中可能需要实现某个功能,而该功能可能用其他语言已经实现了,那么我们可以调用其他语言写好的模块吗?还有就是,由于C#开发好的项目,我们可以利用re ...