《Unix 网络编程》13:守护进程和 inetd 超级服务器
守护进程和 inetd 超级服务器
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
系列文章导航:《Unix 网络编程》笔记
守护进程
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
基本知识
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
守护进程的特点:
- 必须亲自脱离与控制终端的关联,从而避免与作业控制、终端会话管理、终端产生信号等发生任何不期望的交互
- 同时要避免在后台运行的守护进程非预期地输出到终端
守护进程的启动
-
由系统初始化脚本启动,通常位于
/etc/rc
或/etc
目录下,由这些脚本启动的守护进程一开始就有用超级用户特权如:inetd 超级服务器、Web 服务器、sendmail 服务器
-
由 inetd 超级服务器启动,它监听网络请求
-
cron 守护进程按照规则定期执行一些程序,由它执行的程序同样作为守护进程运行
-
at 命令用于在未来某个时刻执行一些程序
-
守护进程还可以从用户终端或在前台或者后台启动,这么做往往是为了测试
daemon_init
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 这是我们自己写的一个函数,把一个普通进程转变为守护进程
- 部分 Unix 变体上提供一个名为 daemon 的 C 库函数,实现类似的功能
由于这段代码涉及的知识点比较多,所以首先需要补充一点操作系统的知识:Linux 进程、进程组、会话周期、控制终端
按照上文中的描述阅读下述代码:
- 在父进程(此时是一个进程组的组长)中使用fork()产生子进程(将来的守护进程由它产生)
- 调用setsid(),用于生成一个新的会话 注意如果当前进程是会话组长时,调用失败。第一点已经可以保证进程不是会话组长了,所以setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话对控制终端的独占性,进程同时与控制终端脱离
- 禁止进程重新打开控制终端 第二步之后,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,在上面的控制终端中已经提到了只有会话组长才能打开控制终端;
#include <syslog.h>
#include "unp.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) // 失败
return (-1);
else if (pid) // 父进程
_exit(0); /* parent terminates */
/* child 1 continues... */
// setsid 使得当前进程变为:
// - 新会话的会话头进程
// - 新进程组的进程组头进程
if (setsid() < 0)
return (-1);
// 忽略 SIGHUP 信号
// 会话头进程终止后,所有其他会话进程都会收到一个该信号
Signal(SIGHUP, SIG_IGN);
if ((pid = Fork()) < 0)
return (-1);
else if (pid)
_exit(0); /* child 1 terminates */
/* child 2 continues... */
// 告知错误处理函数使用 syslog 而不是 fprintf
daemon_proc = 1; /* for err_XXX() functions */
// 改变工作目录,否则就是在当前目录下运行,可能产生破坏
chdir("/");
// 关闭前 64 个描述符,即使有些本身就没有打开
for (i = 0; i < MAXFD; i++)
close(i);
/* redirect stdin, stdout, and stderr to /dev/null */
// 这样设置会占用标准输入、标准输出和标准错误输出的描述符
// 防止在服务器环境下,套接字占用这些描述符,而后误将系统信息发送给这些描述符
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
openlog(pname, LOG_PID, facility);
return (0); /* success */
}
守护进程在没有控制终端的环境下运行,它绝对不会收到来自内核的 SIGHUP
信号,许多守护进程因此把这个信号作为来自系统管理员的一个通知,表示其配置文件已发生改动,守护进程应该重新读取其配置文件。同理还有 SIGINT
和 SIGWINCH
等等
改进时间服务器
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
int main(int argc, char** argv) {
int listenfd, connfd;
socklen_t addrlen, len;
struct sockaddr* cliaddr;
char buff[MAXLINE];
time_t ticks;
// 如果有一些显而易见的错误,则立即抛出,而不是在日志中抛出
if (argc < 2 || argc > 3)
err_quit("usage: daytimetcpsrv2 [ <host> ] <service or port>");
// 使用我们的函数启动
daemon_init(argv[0], 0);
if (argc == 2)
listenfd = Tcp_listen(NULL, argv[1], &addrlen);
else
listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
cliaddr = Malloc(addrlen);
for (;;) {
len = addrlen;
connfd = Accept(listenfd, cliaddr, &len);
err_msg("connection from %s", Sock_ntop(cliaddr, len));
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
}
日志消息
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
syslogd
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
Unix 系统中的 syslogd 守护进程通常由某个系统初始化脚本启动,并且在系统工作期间一直运行
启动的步骤:
- 读取配置:通常为
/etc/syslog.conf
,配置了对消息的处理 - 创建Unix域数据报套接字:给他绑定路径名
/var/run/log
(或/dev/log
) - 创建一个 UDP 套接字:绑定端口 514
- 打开路径名 /dev/klog:来自内核中的任何出错消息从这个“设备”输入
- 监听:调用 select,监听 2 ~ 4 步骤的描述符
- 如果收到消息,读入并按照配置进行处理
- 如果收到
SIGHUP
信号,则重新读取配置文件 (这让我想到了 NGINX 重载配置文件时服务无需暂停)
较新的 syslogd 实现禁止创建 UDP 套接字,因为这样可能会让系统遭到 DOS 攻击,其文件系统可能被填满,从而占满内存空间,或挤掉合法的日志消息
我们一般不会直接向其发送消息,而是通过 syslog 函数写入日志!
syslog
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
守护进程记录消息可以使用 syslog 函数:
#include <syslog.h>
void syslog(int priority, // 优先级
const char *message, // 消息
...)
参数解释:
- priority:好理解,大多日志都有日志级别的功能
- facility:用于标识发送进程类型(函数的参数列表中没有?因为这个要和 priority 用逻辑或使用)
- message:消息内容,类似 printf,不同的是其增加了
%m
规范,用以输出当前 errno 值对应的错误信息
案例
syslog(LOG_INFO|LOG_LOCAL2, "rename(%s, %s): %m", file1, fil)
/etc/syslog.conf
kern.* /dev/debug # 内核的所有消息发送到控制台
local7.debug /var/log/cisco.log # 来自 local7 的所有消息添加到文件 cisco.log 的尾部
一些细节
- 当 syslog 被应用程序首次调用时,它创建一个 Unix 域数据报套接字,然后调用 connect 连接到由 syslogd 守护进程创建的 Unix 域数据报套接字的众所周知路径名(如
/var/run/log
)这个套接字一直保持打开,直到进程终止 - 可以用
openlog
和closelog
来操作上述步骤,在首次调用 syslog 前调用 openlog,不需要发送日志时调用 closelog
openlog 和 closelog
#include <syslog.h>
void openlog(const char* ident, // 日志的前缀,通常是程序名
int options, // 可选值
int facility); // 设置默认的打印设施的值
void closelog(void);
options | 说明 |
---|---|
LOG_CONS | 若无法发送到 syslogd 则登记到控制台 |
LOG_NDELAY | openlog 默认是懒汉加载,这个选项设置不延迟打开,立刻创建套接字 |
LOG_PERROR | 既发送到 syslogd 守护进程,又登记到标准错误输出 |
LOG_PID | 随每个日志消息等级进程 ID |
inetd 守护进程
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
引入 inetd
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
原来的系统服务模型
Unix 上的很多服务,如 SSH、FTP、Telnet 等,我们都可以把它当作一个 Server 端
在系统启动的过程中,这些进程从 /etc/rc
文件中启动,而且每个进程都执行几乎相同的启动任务:
- 创建一个套接字,绑定端口
- 等待连接或数据报的到来
- 派生子进程,子进程提供服务,父进程继续监听
这种方式的缺点
- 所有这些守护进程含有几乎相同的启动代码
- 每个守护进程在进程表中占据一个表项,然而大多数时间他们处于睡眠状态
inted 守护进程
为了解决这种问题,4.3BSD 提供一个因特网超级服务器(即 inetd 守护进程):
- 通过由 inetd 处理普通守护进程的大部分启动细节以简化守护进程的编写,这么一来每个服务器不再有调用
daemon_init
函数的必要 - 单个进程(inetd)就能为多个服务等待外来的客户请求,以取代每个服务器一个进程的做法
inetd 配置文件
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
inetd 使用前文提到的方法把自己变成一个守护进程,然后读取并处理自己的配置文件,通常在 /etc/inetd.conf
,该文件包括如下内容:
字段 | 说明 |
---|---|
service-name | 必须在 /etc/services 文件中定义 |
socket-type | stream(TCP)、dgram(UDP) |
protocol | 必须在 /etc/protocols 文件中定义:tcp或udp |
wait-flag | 对于 TCP 一般为 nowait,对于 UDP 一般为 wait |
login-name | 来自 /etc/passwd 的用户名,一般为 root |
server-program | 调用 exec 指定的完整路径名 |
server-program-arguments | 调用 exec 指定的命令行参数 |
inetd 工作流程
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
其工作流程如下:
-
读入配置文件,为每个服务创建一个合适的套接字
-
为每个套接字调用 bind,指定端口和通配地址
端口号可以通过 getservbyname 获取,用配置文件中的 service-name 和 protocal 字段做参数
-
对于每个 TCP 套接字,调用 listen 以接受外来的连接请求,对于 UDP 则不执行
-
创建完所有套接字后,调用 select 等待其中任何一个套接字变得可读
对于 TCP:
- 当 select 返回可读后,如果该套接字是一个 TCP 套接字,而且其 wait-flag 为 nowait,则调用 accept 接受这个新连接
- inetd 守护进程调用 fork 派生进程,并由子进程处理服务请求,与前面的并发服务器类似,关闭相应的描述符
- 子进程关闭除了要处理的套接字描述符外的所有的描述符,并根据配置文件,切换到相应的用户,用 exec 执行相应的操作
如果设置为 wait,对于 TCP 来说,父进程会用 FD_CLR
禁止这个套接字,保存子进程 ID,当子进程终止(SIGCHILD
)后才继续监听
对于 UDP:
因为数据报服务器只有一个套接字,所以只能一个一个来处理,也就是说通过接受子进程终止的 SIGCHLD
信号后才继续监听
因为假如 fork 后一个子进程,而父进程先于该子进程再次执行,则会由于还没读取数据而再次触发 select 的事件,从而再次 fork 一个无用的子进程
xinetd
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
xinetd 提供与 inetd 一致的基本服务,不过还提供了数目众多的其他特性,包括根据客户的地址登记、接受或拒绝连接的选项等等
daemond_inetd
本文信息 | 本文信息 | 防爬虫替换信息 |
---|---|---|
作者网站 | LYMTICS | //lymtics.top |
作者 | LYMTICS(樵仙) | //lymtics.top |
联系方式 | [email protected] | [email protected] |
原文标题 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 | 《Unix 网络编程》13:守护进程和 inet 超级服务器 |
原文地址 | //www.cnblogs.com/lymtics/p/16341341.html | //www.cnblogs.com/lymtics/p/16341341.html |
- 如果您访问本文的链接并非如上地址,则可能是爬取作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
这个函数的唯一作用就是为错误处理函数设置 daemon_proc 标志,使得运行信息输出在日志中
extern int daemon_proc; /* defined in error.c */
void daemon_inetd(const char* pname, int facility) {
daemon_proc = 1; /* for our err_XXX() functions */
openlog(pname, LOG_PID, facility);
}
修改后的服务器代码:
int main(int argc, char** argv) {
socklen_t len;
struct sockaddr* cliaddr;
char buff[MAXLINE];
time_t ticks;
daemon_inetd(argv[0], 0);
cliaddr = Malloc(sizeof(struct sockaddr_storage));
len = sizeof(struct sockaddr_storage);
Getpeername(0, cliaddr, &len);
err_msg("connection from %s", Sock_ntop(cliaddr, len));
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(0, buff, strlen(buff));
Close(0); /* close TCP connection */
exit(0);
}
- 我们用描述符 0 指代被接受的 TCP 连接
- 因为这个程序针对每一个连接启动一次,所以不需要无限循环
为了启动这个程序,我们需要在 /etc/services
文件中添加:
mydaytime 9999/tcp
在 /etc/inetd.conf
中添加:(在题主的电脑里就只有 xinetd.conf 了)
mydaytime stream tcp nowait andy /foo/bar/daytimetcpsrv3 daytimetcpsrv3
然后给 inetd 发送一个 SIGHUP 信号,告知它重新读取配置文件