《网络编程》守护进程

前言

守护进程是在后台运行并独立于所有终端控制的进程。守护进程没有控制终端源于它们通常是由系统初始化脚本启动,但是也有可能从某个终端由用户在 shell 提示符下键入命令行启动,这种启动方式的守护进程必须亲自脱离与控制终端的关联,从而避免与作业控制、终端会话管理、终端产生信号等发生任何不期望的交互,也可以避免在后台运行的守护进程非预期地输出到终端。有关作业控制、终端控制的内容可参考文章《作业控制、终端控制
和 守护进程

由于守护进程没有控制终端,当守护进程出错时,必须通过某种输出函数输出错误消息,而不能使用标准输出函数。syslog 函数是输出这些消息的标准方法,它把这些消息发送给 syslogd 守护进程。

syslogd 守护进程

Unix 系统中的 syslogd 守护进程通常由某个系统初始化脚本启动,而且在系统工作期间一直运行。其启动步骤如下:

  1. 读取配置文件。通常为 /etc/syslog.conf 的配置文件指定本守护进程如何处理不同类型的日志消息。这些日志消息可能被添加到一个文件,或被写到指定用户的登陆窗口,或被转发给另一个主机上的 syslogd 进程;
  2. 创建一个 Unix 域数据报套接字,给它绑定到路径名 /var/run/log(在某些系统上可能是 /dev/log);
  3. 创建一个 UDP 套接字,给它绑定端口 514(syslog 服务使用的端口号);
  4. 打开路径名 /dev/klog。来自内核中的任何出错消息认为是这个设备的输入;

以下是守护进程产生日志消息的方式:

  1. 内核例程可以调用 log 函数。任何一个用户进程通过打开(open)然后读(read)/dev/klog 设备就可以读这些消息;
  2. 大多数用户进程(守护进程)调用 syslog 函数以产生日志消息,并使日志消息发送至 Unix 域数据报套接字 /dev/log;
  3. 在此主机上的一个用户进程,或通过 TCP/IP 网络连接到此主机的其他主机上的一个用户进程可将日志消息发送到 UDP 端口 514;

syslog 函数

由于守护进程没有控制终端,所以不能把错误日志消息 fprintf 到 stderr 上,但是可以使用 syslog 函数进行处理日志消息。其定义如下:

/* 守护进程 */

#include <syslog.h>

void syslog(int priority, const char *message, ...);

void openlog(const char *ident, int options, int facility);

void closelog(void);

/*
 * 说明:
 * 函数openlog和closelog是可选的,若不调用openlog函数,则在第一次调用syslog函数时,会自动调用openlog函数;
 * openlog可以在首次调用syslog前调用,closelog可以在应用进程不再需要发送日志消息时调用;
 * ident参数是一个由syslog冠于每个日志消息之前的字符串,通常是程序名;
 * ******************************************************************
 * options参数由以下值一个或多个逻辑“或”组成:
 * ******************************************************************
 * (1)LOG_CONS    若无法发送到syslogd守护进程,则将消息登记到控制台
 * (2)LOG_NDELAY  不延迟打开,立即创建套接字
 * (3)LOG_PERROR  既发送到syslogd守护进程,又登记到标准错误输出
 * (4)LOG_PID     每条日志消息都登记进程ID
 * (5)LOG_NOWAIT  不等待在消息记入日志过程中可能已创建的子进程
 * (6)LOG_ODELAY  在记录第一条消息之前延迟打开至syslogd守护进程的连接
 *********************************************************************
 * 参数 priority是级别(level)和设施(facility)两者的组合;
 * level和facility的目的在于允许在/dev/syslog.conf文件中统一配置来自同一个给定设施的所有消息,
 * 或统一配置具有相同级别的所有消息;
 * *********************************************************
 * 其中level取值如下:(注:以下按优先级从高到低)
 * *********************************************************
 * LOG_EMERG    系统不可使用
 * LOG_ALERT    必须立即采取修复
 * LOG_CRIT     临界条件
 * LOG_ERR      出错条件
 * LOG_WARNING  警告条件
 * LOG_NOTICE   正常然而重要的条件(默认值)
 * LOG_INFO     通告消息
 * LOG_DEBUG    调试级消息
 ***********************************************************
 ***********************************************************
 * facility取值如下:
 * *********************************************************
 * LOG_AUTH     安全/授权消息
 * LOG_AUTHPRIV 安全/授权消息(私用)
 * LOG_CRON     cron守护进程
 * LOG_DAEMON   系统守护进程
 * LOG_FTP      FTP守护进程
 * LOG_KERN     内核产生的消息
 * LOG_LOCAL0   保留由本地使用
 * LOG_LOCAL1   保留由本地使用
 * LOG_LOCAL2   保留由本地使用
 * LOG_LOCAL3   保留由本地使用
 * LOG_LOCAL4   保留由本地使用
 * LOG_LOCAL5   保留由本地使用
 * LOG_LOCAL6   保留由本地使用
 * LOG_LOCAL7   保留由本地使用
 * LOG_LPR      行打印机系统
 * LOG_MALL     邮件系统
 * LOG_NEWS     网络新闻系统
 * LOG_SYSLOG   由syslogd内部产生的消息
 * LOG_USER     任意用户级消息(默认)
 * LOG_UUCP     UUCP系统
 * *********************************************************
 */

当 syslog 函数被应用进程首次调用时,它创建一个 Unix 域数据报套接字,然后调用 connect 函数连接到由 syslogd 守护进程创建的 Unix 域数据报套接字的路径名(例如 /var/run/log)。这个套接字一直打开,直到进程终止为止。

当 openlog 函数被应用程序调用时,通常并不立即创建 Unix 域套接字,直到首次调用 syslog 函数时套接字才被创建并打开。但是 openlog 函数可以指定选项 LOG_NDELAY 要求调用 openlog 函数时立即创建套接字。

守护进程编程

守护进程编程步骤

  1. 创建子进程,父进程退出

    ?所有工作在子进程中进行

    ?形式上脱离了控制终端

  2. 在子进程中创建新会话

    ?setsid()函数

    ?使子进程完全独立出来,脱离控制

  3. 改变当前目录为根目录

    ?chdir()函数

    ?防止占用可卸载的文件系统

    ?也可以换成其它路径

  4. 重设文件权限掩码

    ?umask()函数

    ?防止继承的文件创建屏蔽字拒绝某些权限

    ?增加守护进程灵活性

  5. 关闭文件描述符

    ?继承的打开文件不会用到,浪费系统资源,无法卸载

    ?返回所在进程的文件描述符表的项数,即该进程打开的文件数目

守护进程创建的流程图如下:

/* 初始化守护进程 */
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/resource.h>

extern void err_quit(const char *,...);
void daemonize(const char *pname, int facility)
{
    int i, fd0, fd1, fd2;
    pid_t pid;
    struct rlimit rl;
    struct sigaction sa;
    umask(0);

    /* 限制进程资源,参数RLIMIT_NOFILE指定进程不能超过资源最大数 */
    if(getrlimit(RLIMIT_NOFILE, &rl) < 0)
        err_quit("%s: can't get file limit", pname);

    /* 创建子进程*/
    if((pid =fork()) < 0)
        err_quit("%s: can't fork", pname);
    else if(pid != 0)
        exit(0);/* 父进程退出 */
    setsid();/* 创建会话 */

    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if(sigaction(SIGHUP, &sa, NULL) < 0)
        err_quit("%s: can't ignore SIGHUP");
    if((pid =fork()) < 0)
        err_quit("%s: can't fork", pname);
    else if(pid != 0)
        exit(0);
    /* 把工作目录改为根目录 */
    if(chdir("/") < 0)
        err_quit("%s: can't change directory to /");

    if(rl.rlim_max == RLIM_INFINITY)
        rl.rlim_max = 1024;
    /* 关闭所有打开的描述符 */
    for(i = 0; i < (int)rl.rlim_max; i++)
        close(i);

    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);

    /* 使用syslogd处理错误 */
    openlog(pname, LOG_CONS, facility);
    if(fd0 != 0 || fd1 != 1 || fd2 != 2)
    {
        syslog(LOG_ERR, "unexpectrd file descriptors %d %d %d", fd0, fd1, fd2);
        exit(1);
    }

}

上面守护进程编程的具体步骤是:

  1. 首先限制守护进程的资源,然后调用 fork 创建子进程,接着终止父进程,留下子进程继续运行。若本进程是从前台作为一个 shell 命令启动的,当父进程终止时,shell 就认为该命令执行完毕。这样子进程就自动在后台运行。另外,子进程继承了父进程的进程组 ID,但是子进程也有自己的进程 ID,这保证子进程不是一个进程组的首进程,所以必须调用 setsid 函数使子进程称为进程组的首进程;
  2. 调用 setsid 函数创建一个新会话。当前进程变为新会话的会话首进程以及新进程组的进程组首进程,从而不再有控制终端;
  3. 调用 sigaction 函数忽略 SIGHUP 信号,并再次调用 fork 函数,该函数返回时,父进程实际上是上一次调用 fork 产生的子进程,它被终止,留下新的子进程继续运行。再次调用 fork 的目的是确保守护进程将来即使打开一个终端设备时,也不会自动获得控制终端。当没有控制终端的一个会话首进程打开一个设备时,该终端自动成为这个会话首进程的控制终端。然而再次调用 fork 之后,确保新的子进程不再是一个会话首进程,从而不能自动获得一个控制终端。当会话首进程(即首次 fork 产生的子进程)终止时,其会话中的所有进程(即再次
    fork 产生的子进程)都会收到 SIGHUP 信号,所以必须调用 sigaction 函数忽略该信号;
  4. 把工作目录改为根目录。因为守护进程可能在某一个任意文件系统中启动,若不改变工作目录,那么文件系统将无法拆卸;
  5. 关闭本守护进程所有打开的描述符;
  6. 使用 syslogd 处理错误;

inetd 守护进程

inetd 守护进程的特点:

  1. 简化守护进程程序的编写;
  2. 单个进程就能为多个服务等待外来的客户请求;

以下是 inetd 守护进程的工作流程:

inetd 守护进程工作步骤:

  1. 启动阶段,读入/etc/inetd.conf 文件并给该文件中指定的每个服务创建一个适当类型(字节流或数据报)的套接字。新创建的每个套接字都被加入到将由某个 select 调用使用的一个描述符集里面;
  2. 为每个套接字调用 bind 函数,指定捆绑相应服务器的端口和通配地址;
  3. 对于每个 TCP 套接字,调用 listen 函数以接受外来的连接请求;
  4. 创建所有套接字后,调用 select 函数等待其中任何一个套接字变为可读;
  5. 当 select 返回指出某个套接字已可读之后,若是 TCP 套接字,且服务器的 wait-flag 值为 nowait,则调用 accept 接受这个新连接;
  6. inetd 守护进程调用 fork 派生进程,并由子进程处理服务请求;
  7. 子进程关闭除要处理的套接字描述符之外的所有描述符;

以下函数是守护进程化由inetd运行的进程,相对于上面的守护进程编程要简单。

#include	"unp.h"
#include	<syslog.h>

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);
}

参考资料:

《Unix 网络编程》

时间: 2024-10-12 15:47:36

《网络编程》守护进程的相关文章

Python setdaemon守护进程

setdaemon守护进程 #_*_coding:utf-8_*_ __author__ = 'gaogd' import time import threading ''' 守护进程,如果主线程down了,子线程也就没有了. 下面先通过主进程生成main主线程,之后main主线程再生成10个子线程. ''' ''' def run(num):     if not num == 5:         time.sleep(1)     print 'Hi, I am thread %s..la

转:linux守护进程的启动方法

Linux 守护进程的启动方法 作者: 阮一峰 日期: 2016年2月28日 "守护进程"(daemon)就是一直在后台运行的进程(daemon). 本文介绍如何将一个 Web 应用,启动为守护进程. 一.问题的由来 Web应用写好后,下一件事就是启动,让它一直在后台运行. 这并不容易.举例来说,下面是一个最简单的Node应用server.js,只有6行. var http = require('http'); http.createServer(function(req, res)

Python实例浅谈之五Python守护进程和脚本单例运行

一.简介 守护进程最重要的特性是后台运行:它必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符.控制终端.会话和进程组.工作目录以及文件创建掩码等:它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,也可以有作业规划进程crond启动,还可以由用户终端(通常是shell)执行. Python有时需要保证只运行一个脚本实例,以避免数据的冲突. 二.Python守护进程 1.函数实现 #!/usr/bin/env python #coding: utf-8

#python#守护进程的实现

找了整天,终于找到一个可以用的代码 #! /usr/bin/env python2.7 #encoding:utf-8 #@description:一个python守护进程的例子 #@tags:python,daemon import sys import os import time import atexit from signal import SIGTERM      class Daemon:   """   A generic daemon class.     

linux守护进程

#include <iostream>#include <unistd.h>//#include "curl/curl.h"#include "app_curl.h"#include "youtube_package.h"#include "CAutoMail.h"#include <fcntl.h>#include <signal.h>#include <unistd.h

C#开发Linux守护进程

C#开发Linux守护进程 Linux守护进程是Linux的后台服务进程,相当于Windows服务,对于为Linux开发服务程序的朋友来说,Linux守护进程相关技术是必不可少的,因为这个技术不仅仅是为了开发守护进程,还可以拓展到多进程,父子进程文件描述符共享,父子进程通讯.控制等方面,是实现Linux大型服务的基础技术之一. 去年我也曾写了一篇关于守护进程的帖子,名字叫<.NET跨平台实践:用C#开发Linux守护进程>,这篇文章的的确确实现了一个Daemon,不过,它有一个弱点,不能运行多

守护进程的创建过程

编写守护进程需要5步 1 创建子进程,父进程结束(让这个进程由init进程托管) pid = fork(); if(pid > 0) //父进程 { exit(0); }2 在子进程中创建新会话(此进程就可以脱离原来进程,脱离控制终端,脱离原来进程组) setsid(); //最主要是脱离控制终端 3 改变当前目录(每一个进程都有一个当前目录), 不是必须的 chdir("/tmp"); 4 重新设置文件权限掩码(不是必须的) umask(0); 5 关闭打开的文件描述符(如果父

Linux守护进程实现程序只运行一次

1.守护进程 守护进程(Daemon)是一种运行在后台的特殊进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件. 2.让程序只运行一次 如果让程序只运行一次,有很多方法,此处的一种方法是创建一个名字古怪的文件(保证不跟系统文件或其他文件重名),判断文件存在则让程序不再运行且提示程序正在运行,如果不存在则可以运行. 3.测试代码 此代码额外添加了系统LOG,记录操作的信息. 1 #include <stdio.h> 2 #include <unistd.h> 3

Linux系统开发7 进程关系,守护进程

[本文谢绝转载原文来自http://990487026.blog.51cto.com] <大纲> Linux系统开发7  进程关系守护进程 终端 网络终端 Linux PCB结构体信息 进程组 修改子进程.父进程的组ID 会话组 设置一个会话脱离控制终端 生成一个新的会话 守护进程 守护进程模板 获取当前系统时间  终端 在UNIX系统中用户通过终端登录系统后得到一个Shell进程这个终端成为Shell进 程的控制终端Controlling Terminal在讲进程时讲过控制终端是保存在PCB

linux中的守护进程

//一.守护进程 守护进程,也叫精灵进程(daemon) 它和普通后台进程的区别在于以下三点 1.守护进程自成会话,而普通后台进程则不一定 2.守护进程不受终端的控制 3.守护进程就是后台进程,而后台进程不同于守护进程 用ps axj命令查看系统中的进程,TPGID一栏为 -1 的进程(这些进程没有控制终端)就是守护进程. //二.实现 创建守护进程的步骤如下: 1.调用umask把[文件模式创建屏蔽字] 设置为 0 由于 umask 接收的参数会被取反,所以这个 0 传进去取反以后是最大的,也