一 Linux守护进程
Linux 服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户。提供这些服务的程序是由运行在后台的守护进程来执行的。守护进程是生存期长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。linux系统有很多守护进程,大多数服务器都是用守护进程实现的。同时,守护进程完成许多系统任务,比如,作 业规划进程crond、打印进程lqd等。有些书籍和资料也把守护进程称作:“服务”。
守护进程,也就是指daemon和service。
二 Linux守护进程的分类
根据守护进程的启动和管理方式,可以分为独立启动守护进程和超级守护进程两类
独立启动(stand_alone):该类进程启动后就常驻内存,所以会一直占用系统资源。其最大的优点就是它会一直启动,当外界有要求时相应速度较快,像httpd等进程;
超级守护进程:系统启动时由一个统一的守护进程xinet来负责管理一些进程,当相应请求到来时需要通过xinet的转接才可以唤醒被xinet管理的进程。这种进程的优点时最初只有xinet这一守护进程占有系统资源,其他的内部服务并不一直占有系统资源,只有数据包到来时才会被xinet管理员来唤醒。并且我们还可以通过xinet来对它所管理的进程设置一些访问权限,相当于多了一层管理机制。
如果用两个比喻来形容两类守护进程的话一般会用银行的业务处理窗口来类比:
独立启动:银行里有一种单服务的窗口,像取钱,存钱等窗口,这些窗口边上始终会坐着一个人,如果有人来取钱或存钱,可以直接到相应的窗口去办理,这个处理单一服务的始终存在的人就是独立启动的守护进程;
超级守护进程:银行里还有一种窗口,提供综合服务,像汇款,转账,提款等业务;这种窗口附近也始终坐着一个人(xinet),她可能不提供具体的服务,提供具体服务的人在里面闲着聊天啊,喝茶啊,但是当有人来汇款时他会大声喊一句,小王,有人汇款啦,然后里面管汇款的小王会立马跑过来帮忙办完汇款业务。其他的人继续聊天,喝茶。这些负责具体业务的人我们就称之为超级守护进程。当然可能汇款人会有一些规则,可能不能往北京汇款,他就会提早告诉xinet,所以如果有人来汇款想汇往北京的话,管理员就直接告诉他这个我们这里办不到的,于是就根本不会去喊汇款员了,相当于提供了一层管理机制。针对这种窗口还存在多线程和单线程的区别:多线程:将所有用户的要求都提上来,里面的人都别闲着了,都一起干活吧;
单线程:大家都排好队了,一个一个来,里面的人同一时间只有一个人在工作。
这里需要注意的是超级守护进程的管理员xinet也是一个守护进程,只不过它的任务就是传话,其实这也是一个很具体很艰巨的任务哦。
当然每个守护进程都会监听一个端口(银行窗口),一些常用守护进程的监听端口是固定的,像httpd监听80端口, sshd监听22端口等;我们可以将其理解为责任制,时候等待,有求必应。具体的端口信息可以通过cat /etc/services来查看。
三、Linux守护进程管理工具
Linux提供了三种不同的守护进程管理工具:redhat-config-services、ntsysv、chkconfig,可以根据具体需要灵活运用。
# service iptables status #查看相应服务的状态,用service需要服务在/etc/init.d/目录中存在
# netstat -tulp #会列出相应的服务及其监听的端口号等,若加n参数会列出端口号
#chkconfig --list |grep 服务名 #会列出现在当前服务的各种状态,包括在不同运行级别下的启情况,分为上线两部分,上部分是独立启动的服务,你会看到xinetd也在,下面部分是有inet管理的超级守护进程,没有运行级别可分的。
四 Linux守护进程的运行方式
1.独立运行(stand-alone)的守护进程
独立运行的守护进程由init脚本负责管理,所有独立运行的守护进程的脚本在/etc/rc.d/init.d/目录下。系统服务都是独立运行的守护进程,包括syslogd和cron等。独立运行的守护进程的工作方式称做stand-alone,它是UNIX传统的C/S模式的访问模式。stand-alone模式的工作原理如图4-4所示。
工作在stand-alone模式下的网络服务有xinetd、route、gated,另外还有Web服务器Apache和邮件服务器Sendmail、域名服务器Bind。在Linux系统中通过stand-alone模式启动的服务由/etc/rc.d/下面对应的运行级别当中的符号链接启动。
2.xinetd模式运行独立的守护进程
从守护进程的概念可以看出,对于系统所要通过的每一种服务,都必须运行一个监听某个端口连接所发生的守护进程,这通常意味着资源浪费。为了解决这个问题,Linux引进了"网络守护进程服务程序"的概念。Red Hat Linux 9.0使用的网络守护进程是xinted(eXtended InterNET daemon)。xinetd能够同时监听多个指定的端口,在接受用户请求时,它能够根据用户请求的端口的不同,启动不同的网络服务进程来处理这些用户请求。可以把xinetd看做一个管理启动服务的管理服务器,它决定把一个客户请求交给哪个程序处理,然后启动相应的守护进程。xinetd无时不在运行并监听它所管理的所有端口上的服务。当某个要连接它管理的某项服务的请求到达时,xinetd就会为该服务启动合适的服务器。xinetd模式的工作原理如图4-5所示。
3.
xinetd和stand-alone工作模式相比,系统不想要每一个网络服务进程都监听其服务端口,运行单个xinetd就可以同时监听所有服务端口,这样就降低了系统开销,保护系统资源。但是对于访问量大、经常出现并发访问的情况,xinetd则要频繁启动相应的网络服务进程,反而会导致系统性能下降。查看系统为Linux服务提供哪种工作模式,可以在Linux命令行中使用pstree命令,就能看到两种不同模式启动的网络服务。一般来说系统中一些负载高的服务,Sendmail、Apache服务是单独启动的;而其他服务类型都可以使用xinetd超级服务器管理。
五 Xinetd
1.什么是xinetd
xinetd即extended internet daemon,xinetd是新一代的网络守护进程服务程序,又叫超级Internet服务器。经常用来管理多种轻量级Internet服务。xinetd提供类似于inetd+tcp_wrapper的功能,但是更加强大和安全。
2. xinetd的特色
1) 强大的存取控制功能
— 内置对恶意用户和善意用户的差别待遇设定。
— 使用libwrap支持,其效能更甚于tcpd。
— 可以限制连接的等级,基于主机的连接数和基于服务的连接数。
— 设置特定的连接时间。
— 将某个服务设置到特定的主机以提供服务。
2) 有效防止DoS攻击
— 可以限制连接的等级。
— 可以限制一个主机的最大连接数,从而防止某个主机独占某个服务。
— 可以限制日志文件的大小,防止磁盘空间被填满。
3) 强大的日志功能
— 可以为每一个服务就syslog设定日志等级。
— 如果不使用syslog,也可以为每个服务建立日志文件。
— 可以记录请求的起止时间以决定对方的访问时间。
— 可以记录试图非法访问的请求。
4) 转向功能
可以将客户端的请求转发到另一台主机去处理。
5) 支持IPv6
xinetd自xinetd 2.1.8.8pre*起的版本就支持IPv6,可以通过在./configure脚本中使用with-inet6 capability选项来完成。注意,要使这个生效,核心和网络必须支持IPv6。当然IPv4仍然被支持。
6) 与客户端的交互功能
无论客户端请求是否成功,xinetd都会有提示告知连接状态。
3. Xinetd的缺点
当前,它最大的缺点是对RPC支持的不稳定性,但是可以启动protmap,使它与xinetd共存来解决这个问题。
4 使用xinetd启动守护进程
原则上任何系统服务都可以使用xinetd,然而最适合的应该是那些常用的网络服务,同时,这个服务的请求数目和频繁程度不会太高。像DNS和Apache就不适合采用这种方式,而像FTP、Telnet、SSH等就适合使用xinetd模式,系统默认使用xinetd的服务可以分为如下几类。
① 标准Internet服务:telnet、ftp。
② 信息服务:finger、netstat、systat。
③ 邮件服务:imap、imaps、pop2、pop3、pops。
④ RPC服务:rquotad、rstatd、rusersd、sprayd、walld。
⑤ BSD服务:comsat、exec、login、ntalk、shell、talk。
⑥ 内部服务:chargen、daytime、echo、servers、services、time。
⑦ 安全服务:irc。
⑧ 其他服务:name、tftp、uucp。
5. 解读xinet的配置文件/etc/services, /etc/xinetd.conf和/etc/xinetd.d/*
0)/etc/services
在/etc/services 中设置了xinetd下的service对应的端口,例如:
rsync 873/tcp # rsync
rsync 873/udp # rsync
1) /etc/xinetd.conf
xinetd的配置文件是/etc/xinetd.conf,但是它只包括几个默认值及/etc/xinetd.d目录中的配置文件。如果要启用或禁用某项xinetd服务,编辑位于/etc/xinetd.d目录中的配置文件。例如,disable属性被设为yes,表示该项服务已禁用;disable属性被设为no,表示该项服务已启用。/etc/xinetd.conf有许多选项,下面是RHEL
4.0的/etc/xinetd.conf。
# Simple configuration file for xinetd
# Some defaults, and include /etc/xinetd.d/
defaults
{
instances = 60
log_type = SYSLOG authpriv
log_on_success = HOST PID
log_on_failure = HOST
cps = 25 30
}
includedir /etc/xinetd.d
— instances = 60:表示最大连接进程数为60个。
— log_type = SYSLOG authpriv:表示使用syslog进行服务登记。
— log_on_success= HOST PID:表示设置成功后记录客户机的IP地址的进程ID。
— log_on_failure = HOST:表示设置失败后记录客户机的IP地址。
— cps = 25 30:表示每秒25个入站连接,如果超过限制,则等待30秒。主要用于对付拒绝服务攻击。
— includedir /etc/xinetd.d:表示告诉xinetd要包含的文件或目录是/etc/xinetd.d。
2) /etc/xinetd.d/*
下面以/etc/xinetd.d/中的一个文件(rsync)为例。
service rsync
{
disable = yes
socket_type = stream
wait = no
user = root
server = /usr/bin/rsync
log_on_failure += USERID
}
下面说明每一行选项的含义。
— disable = yes:表示禁用这个服务。
— socket_type = stream:表示服务的数据包类型为stream。
— wait = no:表示不需等待,即服务将以多线程的方式运行。
— user = root:表示执行此服务进程的用户是root。
— server = /usr/bin/rsync:启动脚本的位置。
— log_on_failure += USERID:表示设置失败时,UID添加到系统登记表。
5 配置xinetd
1) 格式
/etc/xinetd.conf中的每一项具有下列形式:
service service-name
{
……
}
其中service是必需的关键字,且属性表必须用大括号括起来。每一项都定义了由service-name定义的服务。
service-name是任意的,但通常是标准网络服务名,也可增加其他非标准的服务,只要它们能通过网络请求激活,包括localhost自身发出的网络请求。有很多可以使用的属性,稍后将描述必需的属性和属性的使用规则。
操作符可以是=、+=或-=。所有属性可以使用=,其作用是分配一个或多个值,某些属性可以使用+=或-=,其作用分别是将其值增加到某个现存的值表中,或将其值从现存值表中删除。
2) 配置文件
相关的配置文件如下:
/etc/xinetd.conf
/etc/xinetd.d/* //该目录下的所有文件
/etc/hosts.allow
/etc/hosts.deny
3)/etc/xinetd.conf中的disabled与enabled
前者的参数是禁用的服务列表,后者的参数是启用的服务列表。他们的共同点是格式相同(属性名、服务名列表与服务中间用空格分开,例如disabled
= in.tftpd
in.rexecd),此外,它们都是作用于全局的。如果在disabled列表中被指定,那么无论包含在列表中的服务是否有配置文件和如何设置,都将被禁用;如果enabled列表被指定,那么只有列表中的服务才可启动,如果enabled没有被指定,那么disabled指定的服务之外的所有服务都可以启动。
4) 注意问题
① 在重新配置的时候,下列的属性不能被改变:socket_type、wait、protocol、type;
② 如果only_from和no_access属性没有被指定(无论在服务项中直接指定还是通过默认项指定),那么对该服务的访问IP将没有限制;
③ 地址校验是针对IP地址而不是针对域名地址。
6 xinetd防止拒绝服务攻击(Denial of Services)的原因
xinetd能有效地防止拒绝服务攻击(Denial of Services)的原因如下。
1) 限制同时运行的进程数
通过设置instances选项设定同时运行的并发进程数:
instances=20
当服务器被请求连接的进程数达到20个时,xinetd将停止接受多出部分的连接请求。直到请求连接数低于设定值为止。
2) 限制一个IP地址的最大连接数
通过限制一个主机的最大连接数,从而防止某个主机独占某个服务。
per_source=5
这里每个IP地址的连接数是5个。
3) 限制日志文件大小,防止磁盘空间被填满
许多攻击者知道大多数服务需要写入日志。入侵者可以构造大量的错误信息并发送出来,服务器记录这些错误,可能就造成日志文件非常庞大,甚至会塞满硬盘。同时会让管理员面对大量的日志,而不能发现入侵者真正的入侵途径。因此,限制日志文件大小是防范拒绝服务攻击的一个方法。
log_type FILE.1 /var/log/myservice.log 8388608 15728640
这里设置的日志文件FILE.1临界值为8MB,到达此值时,syslog文件会出现告警,到达15MB,系统会停止所有使用这个日志系统的服务。
4) 限制负载
xinetd还可以使用限制负载的方法防范拒绝服务攻击。用一个浮点数作为负载系数,当负载达到这个数目的时候,该服务将暂停处理后续的连接。
max_load = 2.8
上面的设定表示当一项系统负载达到2.8时,所有服务将暂时中止,直到系统负载下降到设定值以下。
说明 要使用这个选项,编译时应加入“--with-loadavg”,xinetd将处理max-load配置选项,从而在系统负载过重时关闭某些服务进程,来实现防范某些拒绝服务攻击。
5) 限制所有服务器数目(连接速率)
xinetd可以使用cps选项设定连接速率,下面的例子:
cps = 25 60
上面的设定表示服务器最多启动25个连接,如果达到这个数目将停止启动新服务60秒。在此期间不接受任何请求。
6) 限制对硬件资源的利用
通过rlimit_as和rlimit_cpu两个选项可以有效地限制一种服务对内存、中央处理器的资源占用:
rlimit_as = 8M
rlimit_cpu=20
上面的设定表示对服务器硬件资源占用的限制,最多可用内存为8MB,CPU每秒处理20个进程。
xinetd的一个重要功能是它能够控制从属服务可以利用的资源量,通过它的以上设置可以达到这个目的,有助于防止某个xinetd服务占用大量资源,从而导致“拒绝服务”情况的出现。
六 Service命令
Linux的service命令就是查看和控制所有的独立启动的守护进程。 这个命令不是在所有的linux发行版本中都有。主要是在redhat系linux中。service此命令位于/sbin/service,用file命令查看此命令会发现它是一个脚本命令。分析脚本可知此命令的作用是去/etc/init.d目录下寻找相应的服务,进行开启和关闭等操作。例如service mysqld stop等价于/etc/init.d/mysqld stop。
七 xinetd本身也是一个独立的守护进程,在/etc/init.d/xinetd。
参考:
http://book.51cto.com/art/200906/127264.htm
http://wordpress.facesoho.com/server/what-is-xinetd.html
http://wbwk2005.blog.51cto.com/2215231/400260
http://hi.baidu.com/neubuffalo/blog/item/3d48148291be90b76d8119e1.html
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 ");
- syslog(LOG_INFO, buff);
- ticks = time(NULL);
- snprintf(buff, sizeof(buff), "%.24s ", 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 ", 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 ", 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 ");
- 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 ", 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 ", 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 ", 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 ", buff);
- ticks = time(NULL);
- snprintf(buff, sizeof(buff), "%.24s ", 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