六十一、linux 编程—— 守护进程
61.1 介绍
- 守护进程(daemon)是生存期长的一种进程。它们常常在系统引导装入时启动,在系统关闭时终止
- 守护进程也称为后台进程
- 所有守护进程都以超级用户(用户 ID 为0)的优先权运行。
- 守护进程没有控制终端
- 守护进程的父进程都是 init 进程
62.2 例子
62.2.1 编程步骤
- 使用 umask 将文件模式创建屏蔽字设置为0
- 调用 fork ,然后让父进程退出(exit)
- 调用 setsid 创建一个新会话
- 将当前工作目录更改为根目录
- 关闭不需要的文件描述符
62.2.2 守护进程出错处理
- 由于守护进程完全脱离了控制终端,因此,不能像其他程序一样通过输出错误信息到控制台的方式来通知程序员
- 通常办法是使用 syslog 服务,将出错信息输出到"/var/log/syslog" 系统日志文件中去
- syslog 是 linux 中的系统日志管理服务,通过守护进程 syslog 来维护
- openlog 函数用于打开系统日志服务的一个连接
- syslog 函数用于向日志文件中写入信息,在这里可以规定消息的优先级、消息的输出格式等
- closelog 函数用于关闭系统日志服务的连接
62.2.3 函数原型



61.2.4 例子
echo_tcp_server_daemon.c
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "vector_fd.h" vector_fd *vfd;
int sockfd; /**
* fd 对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
*/
void do_service(int fd)
{
char buff[];
memset(buff, , sizeof(buff)); /**
* 因为采用非阻塞方式,若读不到数据直接返回,
* 直接服务于下一个客户端,
* 因此不需要判断 size < 0 的情况 */
ssize_t size = read(fd, buff, sizeof(buff)); if(size == ){
/** 客户端已经关闭连接 */
syslog(LOG_DEBUG, "client closed");
/** 从动态数组中删除对应的 fd */
remove_fd(vfd, fd);
/** 关闭对应客户端的 socket */
close(fd);
}
else if(size > ){
syslog(LOG_DEBUG, "%s", buff);
if(write(fd, buff, size) < ){
if(errno == EPIPE){
/** 客户端关闭连接 */
syslog(LOG_DEBUG, "write error:%s\n", strerror(errno));
remove_fd(vfd, fd);
close(fd);
}
}
}
} void out_addr(struct sockaddr_in *clientaddr)
{
char ip[];
memset(ip, , sizeof(ip));
int port = ntohs(clientaddr->sin_port);
inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
syslog(LOG_DEBUG, "%s(%d) connected!\n", ip, port);
} /** 遍历出动态数组中所有的描述符并加入到描述符集 set
* 中,同时此函数返回动态数组中最大的那个描述符 */
int add_set(fd_set *set)
{
FD_ZERO(set); ///< 清空描述符集
int max_fd = vfd->fd[0];
int i = ;
for(; i < vfd->counter; i++){
int fd = get_fd(vfd, i);
if(fd > max_fd) max_fd = fd;
FD_SET(fd, set); ///< 将 fd 加入到描述符集中
} return max_fd;
} void *th_fn(void *arg)
{
/** 设置超时时间 2s */
struct timeval t;
t.tv_sec = ;
t.tv_usec = ; int n = ;
int maxfd;
fd_set set; ///< 描述符集
maxfd = add_set(&set); /**
* 调用 select 函数会阻塞,委托内核去检查传入的描述符是否准备好,
* 若有则返回准备好的描述符;超时则返回 0
* 第一个参数为描述符集中的描述符的范围(最大描述符 + 1)
*/
while((n = select(maxfd + , &set, NULL, NULL, &t)) >= ){
/** 检测哪些描述符准备好, 并和这些准备好的描述符对应的客户端进行数据的双向通信 */
if(n > ){
int i = ;
for(; i < vfd->counter; i++){
int fd = get_fd(vfd, i);
if(FD_ISSET(fd, &set)){
do_service(fd);
}
}
}
/** 重新设置时间和清空描述符集 */
t.tv_sec = ;
t.tv_usec = ;
/** 重新遍历动态数组中最新的描述符放置到描述符集中 */
maxfd = add_set(&set);
} return (void *);
} int main(int argc, char *argv[])
{
if(argc < ){
printf("usage: %s #port\n", argv[]);
exit();
} /** 守护进程编程的5个步骤 */
/** 步骤1: 创建屏蔽字为 0 */
umask();
/** 步骤2: 调用 fork 函数创建子进程,然后父进程退出 */
pid_t pid = fork();
if(pid > ) exit();
/** 步骤3: 调用 setsid 函数创建一个新会话 */
setsid();
/** 步骤4: 将当前工作目录更改为更目录 */
chdir("/");
/** 步骤5: 关闭不需要的文件描述符 */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO); /** 打开系统日志服务的一个连接 */
openlog(argv[], LOG_PID, LOG_SYSLOG); /** 步骤1: 创建 socket(套接字)
* 注: socket 创建在内核中,是一个结构体.
* AF_INET: IPV4
* SOCK_STREAM: tcp 协议
* AF_INET6: IPV6
*/
sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < ){
/** 将日志信息写入到系统日志文件中(/var/log/syslog) */
syslog(LOG_DEBUG, "socket:%s\n", strerror(errno));
exit();
} /**
* 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
*/
struct sockaddr_in serveraddr;
memset(&serveraddr, , sizeof(struct sockaddr_in));
/** 往地址中填入 ip、port、internet 地址族类型 */
serveraddr.sin_family = AF_INET; ///< IPV4
serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
syslog(LOG_DEBUG, "bind:%s\n", strerror(errno));
exit();
} /**
* 步骤3: 调用 listen 函数启动监听(指定 port 监听)
* 通知系统去接受来自客户端的连接请求
* (将接受到的客户端连接请求放置到对应的队列中)
* 第二个参数: 指定队列的长度
*/
if(listen(sockfd, ) < ){
syslog(LOG_DEBUG, "listen:%s\n", strerror(errno));
exit();
} /** 创建放置套接字描述符 fd 的动态数组 */
vfd = create_vector_fd(); /** 设置线程的分离属性 */
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int err;
if((err = pthread_create(&th, &attr, th_fn, (void *))) != ){
syslog(LOG_DEBUG, "pthread create:%s\n", strerror(errno));
exit();
}
pthread_attr_destroy(&attr); /**
* 1)主控线程获得客户端的链接,将新的 socket 描述符放置到动态数组中
* 2) (a)启动的子线程调用 select 函数委托内核去检查传入到 select
* 中的描述符是否准备好.
* (b)利用 FD_ISSET 来找出准备好的那些描述符,
* 并和对应的客户端进行双向通信(非阻塞)
*/
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr); while(){
/**
* 步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
* socket 描述符
* 注意: 若没有客户端连接,调用此函数后会阻塞, 直到获得一个客户端的连接
*/
/** 主控线程负责调用 accept 去获得客户端的连接 */
int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
if(fd < ){
syslog(LOG_DEBUG, "accept:%s\n", strerror(errno));
continue;
} out_addr(&clientaddr); /** 将返回的新的 socket 描述符加入到动态数组中 */
add_fd(vfd, fd);
}
closelog(); return ;
}
编译运行如下:

查看 /var/log/syslog/

六十一、linux 编程—— 守护进程的更多相关文章
- linux C守护进程编写
linux编程-守护进程编写 守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待 处理某些发生的事件.守护进程是一种很有用的进程. Linux的大多数服 ...
- 【Linux】- 守护进程的启动方法
转自:Linux 守护进程的启动方法 Linux中"守护进程"(daemon)就是一直在后台运行的进程(daemon). 本文介绍如何将一个 Web 应用,启动为守护进程. 一.问 ...
- python实现Linux启动守护进程
python实现Linux启动守护进程 DaemonClass.py代码: #/usr/bin/env python # -*- coding: utf-8 -*- import sys import ...
- linux 创建守护进程的相关知识
linux 创建守护进程的相关知识 http://www.114390.com/article/46410.htm linux 创建守护进程的相关知识,这篇文章主要介绍了linux 创建守护进程的相关 ...
- asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二)
原文:asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二) 续上一篇文章:asp.net core2.0 部署centos7/linux系统 -- ...
- linux系统编程--守护进程,会话,进程组,终端
终端: 在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal), 进程中,控制终端是保存在PCB中的信息,而f ...
- Linux系统编程---守护进程
守护进程是什么?就是在后台运行的进程. 那么如何创建守护进程呢? 1. 创建孤儿进程 2. setsid() 创建进程会话 3. 重定向标准输入, 标准输出 4. chdir, 改当当前进程的工作目录 ...
- 编写Linux/Unix守护进程
原文: http://www.cnblogs.com/haimingwey/archive/2012/04/25/2470190.html 守护进程在Linux/Unix系统中有着广泛的应用.有时,开 ...
- Python之路(第三十六篇)并发编程:进程、同步异步、阻塞非阻塞
一.理论基础 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内容都是围绕进程的概念展开的. 即使可以利用的cpu只有一个(早期的 ...
随机推荐
- hbase 过滤器 rowfilter
HBase为筛选数据提供了一组过滤器,通过这个过滤器可以在HBase中的数据的多个维度(行,列,数据版本)上进行对数据的筛选操作,也就是说过滤器最终能够筛选的数据能够细化到具体的一个存储单元格上(由行 ...
- docker下编译mangoszero WOW60级服务端(三)
开始构建WOW服务端通用镜像 第二篇文章中准备工作环节已经从github拉取了mangosd源代码,这里我们就可以直接开始编写dockerfile并进行编译 (1) 进入mangos/wow60/ma ...
- 查询本地电脑IP地址
使用Windows+R键打开"运行"窗口,然后输入CMD进入命令提示窗口 进入命令窗口之后,输入:ipconfig/all 回车即可看到整个电脑的详细的IP配置信息
- Luogu4655 [CEOI2017]Building Bridges
Luogu4655 [CEOI2017]Building Bridges 有 \(n\) 根柱子依次排列,每根柱子都有一个高度.第 \(i\) 根柱子的高度为 \(h_i\) . 现在想要建造若干座桥 ...
- SpringBoot开发案例之打造私有云网盘
前言 最近在做工作流的事情,正好有个需求,要添加一个附件上传的功能,曾找过不少上传插件,都不是特别满意.无意中发现一个很好用的开源web文件管理器插件 elfinder,功能比较完善,社区也很活跃,还 ...
- API简介
概述 API(Application Programming Interface),应用程序编程接口.Java API是一本程序员的 字典 ,是JDK中提供给我们使用的类的说明文档.这些类将底层的代码 ...
- 二、PHP基本语法 - PHP零基础快速入门
我们日常生活中,有些人使用普通话交流,有些人使用家乡话.类比到计算机的世界里,PHP 是人与计算机沟通的语言之一. 既然是语言,那就必须遵循一定的语法规则.譬如 A 向 B 表白,A 会对 B 说:& ...
- Appium 客户端类库
Appium 支持以下语言的客户端类库: 语言 Ruby Python Java JavaScript PHP C# Objective-C 锁定注意,一些方法类似 endTestCoverage() ...
- MySQL之开发规范
一..数据库命名规范 1.所有数据库对象名称必须使用小写字母并用下划线分割 2.所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 3.数据库对象的命 ...
- PHP 高级工程面试题汇总
PHP高级工程面试题汇总(2018.05) 1.给你四个坐标点,判断它们能不能组成一个矩形,如判断([0,0],[0,1],[1,1],[1,0])能组成一个矩形. 勾股定理,矩形是对角线相等的四边形 ...