以下知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。
1、守护进程(daemon)
在后台运行且不与任何控制终端关联的进程——守护进程。
守护进程产生的所有输出通常通过调用syslog函数发送给syslogd守护进程。系统管理员可根据发送消息的守护进程以及消息的严重级别,完全控制这些消息的处理方式。
守护进程多种启动方法:
i、在系统启动阶段,许多守护进程由系统初始化脚本启动。该脚本通常位于/etc目录或以/etc/rc开头的某个目录中,它们的具体位置和内容却是实现相关的。由这些脚本启动的守护进程一开始时拥有超级用户特权;
ii、许多网络服务器由inetd超级服务器启动(后续解释一下超级二字,个人理解)。而inetd自身由上一条中的某个脚本启动。直白一点就是,inetd监听网络请求(Telnet、FTP等),每当有一个请求到达时,启动相应的实际服务器(Telnet服务器、FTP服务器等);
iii、cron守护进程按照规则定期执行一些程序,而由它启动执行的程序同样作为守护进程运行。其自身由上一条中的某个脚本启动;
iv、at命令用于指定将来某个时刻的程序执行,最后还是转换成cron执行;
v、守护进程还可以从用户终端或在前台或在后台启动。目的是为了测试守护程序或重启因某种原因而终止了的某个守护进程。
2、daemon_init函数
通过调用该函数,能够把一个普通进程转变为守护进程。其代码如下:#include "unp.h"
#include <syslog.h>
#define MAXFD 64
extern int daemon_proc; /* defined in error.c */
int
daemon_init(const char *pname, int facility)
{
int i;
pid_t pid;
if ((pid = Fork()) < 0)//fork在父进程中,返回子进程号,在子进程中返回0
return (-1);
else if (pid)
_exit(0); /* parent terminates *///终止父进程
/* child 1 continues... */
if (setsid() < 0) /* become session leader */
return (-1);
Signal(SIGHUP, SIG_IGN);
if ((pid = Fork()) < 0)//再进行一次fork,确保新的子进程不再是一个会话头进程(因为没有终端控制台哈!)
return (-1);
else if (pid)
_exit(0); /* child 1 terminates */
/* child 2 continues... */
daemon_proc = 1; /* for err_XXX() functions */
chdir("/"); /* change working directory */
/* close off file descriptors */
for (i = 0; i < MAXFD; i++)//关闭本守护进程从执行它的进程继承来的所有打开着的描述符
close(i);
/* redirect stdin, stdout, and stderr to /dev/null */
open("/dev/null", O_RDONLY);//打开/dev/null作为本守护进程的标准输入、输出、错误输出
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
openlog(pname, LOG_PID, facility);//之后,会使用syslogd处理错误
//LOG_PID把进程ID加到每个日志消息中,facility用于标识消息发送进程类型
return (0); /* success */
}
daemon_init函数调用的具体实例见书上P292,亲测有效!
3、inetd守护进程
特点:
i、通过由inetd处理普通守护进程的大部分启动细节以简化守护程序的编写;
ii、单个进程(inetd)就能为多个服务等待外来的客户请求,以此取代每个服务一个进程的做法。减少了系统中的进程总数。
inetd进程使用随daemon_init函数的特点把自己演成一个守护进程,接着读入并处理自己的配置文件。
inetd守护的工作流程(见下图分析):
i、在启动阶段,读入/etc/inetd.conf文件并给该文件中指定的每个服务创建一个适当类型(字节流或数据报)的套接字。inetd能够处理的服务器的最大数目取决于inetd能够建的描述符的最大数目。新创建的每个套接字都被加入将由某个select调用使用的一个描述符集中。
ii、为每个套接字调用bind,指定捆绑相应服务器的众所周知端口和通配地址。tcp或udp端口号通过调用getservbyname获得,作为函数参数的是相应服务器在配置文件中的service-name字段和protocol字段。
iii、对于每个tcp套接字,调用listen以接受外来的连接请求。udp就免了。
iv、创建完毕所有套接字之后,调用select等待其中任何一个套接字变为可读。inetd的大部分时间花在阴塞于select调用内部,等待某个套接字变为可读。
v、当select返回指出某个套接字已可读之后,如果该套接字是一个tcp套接字,而且其服务器的wait-flag值为nowait,那就调用accept接受这个新连接。
vi、inetd守护进程调用fork派生进程,并由子进程处理服务请求。
vii、如果第5步中select返回的是一个字节流套接字,那么父进程必须关闭已连接套接字。父进程再次调用select,等待下一个变为可读的套接字。
实践操作步骤:
在/etc/inetd.conf的配置文件指定本超级服务器处理哪些服务以及当一个服务请求到达时该怎么做。/etc/inetd.conf文件中每行包含的字段如下表:
字段 | 说明 |
service-name | 必须在/etc/servces文件中定义 |
socket_type | stream或dgram |
protocol | 必须在/etc/protocols文件中定义,tcp或udp |
wait-flag | 对于tcp一般为nowait,对于udp一般为wait |
login-name | 来自/etcpasswd的用户名,一般为root,我自已当然是tt |
server-program | 调用exec指定的完整路径名 |
server-program-arguments | 调用exec指定的命令行参数 |
举例:
在/etc/inetd.conf文件中添加一行如下:(此处服务名:mydaytime,已在/etc/services中设置,如mydaytime 9999/tcp)
mydaytime stream tcp nowait tt /home/tt/unpv13e/inetd/daytimetcpsrv3 daytimetcpsrv3
重新启动服务如下,于是daytimetcpsrv3便作为守护进程于后台运行
/etc/init.d/xinetd restart //重新启动inetd超级服务器
/etc/init.d/xinetd stop //关闭inetd超级服务器
可通过netstat -tua命令查看。
可通过ps -aux命令查看所有进程
通过telnet sjtu 9999 连接上述运行的服务器。