5. 守护进程的介绍
5.1 守护进程
(1)守护进程(daemon)是生存期长的一种进程。它们常常在系统引导装入时启动,在系统关闭时终止。
(2)所有守护进程者都以超级用户(用户ID为0)的优先权运行。
(3)守护进程没有控制终端
(4)守护进程的父进程都是init进程
5.2 守护进程的编程步骤
(1)使用umask将文件模式创建屏蔽字设置为0。(由继承而来的文件模式创建屏蔽字可能会拒绝设置某些权限。如若守护进程要创建一组可读、写的文件,而继承的文件模式可能屏蔽了这两种权限)
(2)调用fork,然后让父进程退出(exit)(子进程成为孤儿进程,将由init领养)
(3)调用setsid创建一个新会话。(因为普通进程都和运行该进程的控制台处于同一会话中,表现为如果终端被关闭了,则这个终端中运行的所有进程都会被关闭)
(4)将当前工作目录更改为根目录。(因为守护进程通常在系统再启动之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载的文件系统中,那么该文件系统就不能被卸载)
(5)关闭不需要的文件描述符(如守护进程没有终端,也就不需要标准输入和输出了)
5.3 出错处理
(1)由于守护进程完全脱离了控制终端。因此,不能像其他程序一样通过输出错误信息到控制台的方式通知我们。
(2)通常使用syslog服务,将出错信息输入到/var/log/syslog系统日志文件中去。(Centos6.x中己经找不到该文件)
(3)syslog是linux中的系统日志管理服务通过守护进程syslog来维护。
5.4 syslog服务
(1)使用方法(centos6.x中该服务己被rsyslogd服务所替代)
①openlog函数用于打开系统日志服务的一个连接。
②syslog函数用于向日志文件中写入消息,在这里可以规定消息的优先级、消息的输出格式等。
③closelog函数用于关闭系统日志服务的连接。
(2)openlog函数
头文件 |
#include <syslog.h> |
函数 |
void openlog(char* ident, int option, int facility); |
参数 |
ident:要向每个消息加入的字符串,通常为程序的名称。 option参数: ①LOG_CONS:若日志消息不能通过发送到syslog,则将该消息写至控制台。 ②LOG_NDELAY:立即打开linux域数据报套接字至syslog守护进程。通常,在记录第一条消息之前,该套接字不打开。 ③LOG_PERROR:除将日志消息发送到syslog外,还将它写至stderr。 ④LOG_PID:每条消息都包含进程ID,此选择项可供对每个请求都fork一个子进程的守护进程使用。 facility参数:用来指定记录消息程序的类型 LOG_AUTH(授权程序如login、su、getty等)、LOG_CRON(cron和at)、LOG_DAEMON(系统守护进程,如ftpd、routed等)、LOG_KERN(内核产生的消息)、LOG_LOCAL0~7(保留由本地使用)、LOG_LPR(行打印系统,如lpd、lpc等)、LOG_MAIN(邮件系统)、LOG_NEWSU(senet网络新闻系统)、LOG_SYSLOG(syslogd守护进程本身)、LOG_USER(来自其他用户进程的消息)、LOG_UUCP(UUCP系统) |
功能 |
打开日志文件 |
(3)syslog和closelog函数
头文件 |
#include <syslog.h> |
函数 |
void syslog(int priority, char* format,…); void closelog(void) |
参数 |
priority参数:消息优先级 ①LOG_EMERG:紧急(系统不可使用,最高优先级)。 ②LOG_ALERT:必须立即修复的状态 ③LOG——CRIT:严重状态(例如,硬设备出错) ④LOG_ERR:出错状态 ⑤LOG_WARNING:警告状态 ⑥LOG_NOTICE:正常,但重要的状态 ⑦LOG_INFO:信息性消息 ⑧LOG_DEBUG:调试消息(最低优先级) format:要写入的内容(类似于printf格式化后的内容) |
功能 |
写日志和关闭日志文件 |
【编程实验】echo服务器(守护进程)
//vector_fd.h(与前面的例子相同)
#ifndef __VECTOR_H__ #define __VECTOR_H__ #include <pthread.h> //用于存放sock的动态数组(线程安全!) typedef struct{ int *fd; int counter; //元素个数 int max_counter;//最多存数个数,会动态增长 pthread_mutex_t mutex; }VectorFD, *PVectorFD; //动态数组相关的操作函数 extern VectorFD* create_vector_fd(void); extern void destroy_vector_fd(VectorFD* vfd); extern int get_fd(VectorFD* vfd, int index); extern void remove_fd(VectorFD* vfd, int fd); extern void add_fd(VectorFD* vfd, int fd); #endif
//vector_fd.c(与前面的例子相同)
#include "vector_fd.h" #include <memory.h> #include <malloc.h> #include <assert.h> //查找指定fd在数组中的索引值 static int indexof(VectorFD* vfd, int fd) { int ret = -1; int i=0; for(; i<vfd->counter; i++){ if(vfd->fd[i] == fd){ ret = i; break; } } return ret; } //数组空间的动态增长 static void encapacity(VectorFD* vfd) { if(vfd->counter >=vfd->max_counter){ int* fds = (int*)calloc(vfd->counter + 5, sizeof(int)); assert(fds != NULL); memcpy(fds, vfd->fd, sizeof(int) * vfd->counter); free(vfd->fd); vfd->fd = fds; vfd->max_counter += 5; } } //动态数组相关的操作 VectorFD* create_vector_fd(void) { VectorFD* vfd = (VectorFD*)calloc(1, sizeof(VectorFD)); assert(vfd != NULL); //分配存放fd的数组空间 vfd->fd = (int*)calloc(5, sizeof(int)); assert(vfd->fd != NULL); vfd->counter = 0; vfd->max_counter = 0; //对互斥锁进行初始化 pthread_mutex_init(&vfd->mutex, NULL); return vfd; } void destroy_vector_fd(VectorFD* vfd) { assert(vfd != NULL); //销毁互斥锁 pthread_mutex_destroy(&vfd->mutex); free(vfd->fd); free(vfd); } int get_fd(VectorFD* vfd, int index) { int ret = 0; assert(vfd != NULL); pthread_mutex_lock(&vfd->mutex); if((0 <= index) && (index < vfd->counter)){ ret = vfd->fd[index]; } pthread_mutex_unlock(&vfd->mutex); return ret; } void remove_fd(VectorFD* vfd, int fd) { assert(vfd != NULL); pthread_mutex_lock(&vfd->mutex); int index = indexof(vfd, fd); if(index >= 0){ int i = index; for(; i<vfd->counter-1; i++){ vfd->fd[i] = vfd->fd[i+1]; } vfd->counter--; } pthread_mutex_unlock(&vfd->mutex); } void add_fd(VectorFD* vfd, int fd) { assert(vfd != NULL); encapacity(vfd); vfd->fd[vfd->counter++] = fd; }
//echo_tcp_server_daemon.c
#include <netdb.h> #include <sys/socket.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <memory.h> #include <time.h> #include <pthread.h> #include <signal.h> #include <errno.h> #include "vector_fd.h" #include <fcntl.h> #include <syslog.h> /*基于select模型的服务程序(守护进程) 测试:telnet 127.0.0.1 xxxx http://xxx.xxx.xxx.xxx:端口号 注意:演示时可关闭服务器的防火墙,防火墙口被过滤 #service iptables status 查看防火墙 #service iptables stop 关闭防火墙 */ VectorFD* vfd; int sockfd; int bStop = 0; void out_addr(struct sockaddr_in* clientAddr) { char ip[16]; memset(ip, 0, 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); } /*服务程序 * fd对应于某个连接的客户端,和某一个连接的客户端进行双向通信 */ void do_service(int fd) { /*服务端和客户端进行读写操作(双向通信)*/ char buff[512]; memset(buff, 0, sizeof(buff)); size_t size = read(fd, buff, sizeof(buff)); //读取客户端发送过来的消息 //若读不到数据直接返回了,直接服务于下一个客户端 //因此不需要判断size小于0的情况。 if(size == 0){ //客户端己关闭连接 syslog(LOG_DEBUG, "client closed\n"); //将fd从动态数组中删除 remove_fd(vfd, fd); close(fd); }else if(size > 0){ syslog(LOG_DEBUG, "%s\n", buff); //显示客户端发送的消息 //写回客户端(回显功能) if(write(fd, buff, sizeof(buff)) != size){ if(errno == EPIPE){ //如果客户端己被关闭(相当于管道的读端关闭),会产生SIGPIPE信号 //并将errno设置为EPIPE syslog(LOG_DEBUG, "write error\n"); remove_fd(vfd, fd); close(fd); } } } } //遍历动态数组中所有的socket描述符,并将之加入到fd_set中。 //同时此函数返回动态数组中最大的那个描述符 static int add_set(fd_set* set) { FD_ZERO(set); //清空描述符集 int max_fd = vfd->fd[0]; int i=0; for(; i<vfd->counter; i++){ int fd = get_fd(vfd, i); if(fd > max_fd) max_fd = fd; FD_SET(fd, set); //将fd加入到fd_set中 } return max_fd; } //线程函数 void* th_fn(void* arg) { struct timeval t; t.tv_sec = 2; t.tv_usec = 0; int n = 0; //返回select返回的准备好的socket数量 int maxfd; //所有socket描述符的最大值 fd_set set; maxfd = add_set(&set); /* * 调用select函数会阻塞,委托内核去检查传入的描述符集是否有socket己准备好, * 若有,则返回准备好的socket数量,超时则返回0 * 第1个参数为fd_set中socket的范围(最大描述符+1) */ while(((n = select(maxfd + 1, &set, NULL, NULL, &t)) >=0) && (!bStop)){ if(n > 0){ int i = 0; //检测哪些socket准备好,并和这些准备好的socket对应的客户端进行双向通信 for(; i<vfd->counter; i++){ int fd = get_fd(vfd, i); if(FD_ISSET(fd, &set)){ do_service(fd); } } } //重新设置时间 t.tv_sec = 2; t.tv_usec = 0; //清空描述符集 //重新遍历动态数组中最新的描述符,并放置到fd_set maxfd = add_set(&set); } return (void*)0; } int main(int argc, char* argv[]) { if(argc < 2){ printf("usage: %s port\n", argv[0]); exit(1); } /*创建守护进程的5个步骤*/ //步骤1: 创建屏蔽字为0 umask(0); //步骤2: 调用fork,创建子进程,然后父进程退出 pid_t pid = fork(); if(pid > 0) exit(0); //步骤3: 调用setsid函数创建一个新会话 setsid(); //步骤4:将当前工作目录更改为根目录 chdir("/"); //步骤5: 关闭不需要的文件描述符 close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); //打开系统日志服务的一个连接 openlog(argv[0], LOG_PID, LOG_SYSLOG);//由syslogd服务本身来记录 /*步骤1:创建socket(套接字) *注:socket创建在内核中,是一个结构体 *AF_INET:IPv4 *SOCK_STREAM:tcp协议 */ sockfd = socket(AF_INET, SOCK_STREAM, 0); /*步骤2:将sock和地址(包括ip、port)进行绑定*/ struct sockaddr_in servAddr; //使用专用地址结构体 memset(&servAddr, 0, sizeof(servAddr)); //往地址中填入ip、port和Internet地址族类型 servAddr.sin_family = AF_INET;//IPv4 servAddr.sin_port = htons(atoi(argv[1])); //port servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) <0 ){ //将日志信息写入到系统日志文件中(/var/log/syslog) syslog(LOG_DEBUG, "bind: %s\n", strerror(errno)); exit(1); } /*步骤3:调用listen函数启动监听 * 通知系统去接受来自客户端的连接请求 */ if(listen(sockfd, 10) < 0){ //队列中最多允许10个连接请求 //将日志信息写入到系统日志文件中(/var/log/syslog) syslog(LOG_DEBUG, "listen: %s\n", strerror(errno)); exit(1); } //创建放置套接字描述符的动态数组 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*)0)) != 0){ syslog(LOG_DEBUG, "pthread create: %s\n", strerror(errno)); exit(1); } pthread_attr_destroy(&attr); /*(1)主线程获得客户端连接,将新的socket描述符放置到动态数组中 *(2)子线程的任务 A.调用select委托内核去检查传入到select中的描述符是否准备好 * B.利用FD_ISSET来找出准备好的那些描述符并和对应的客户端进行双向通信 */ struct sockaddr_in clientAddr; socklen_t len = sizeof(clientAddr); while(!bStop){ /*步骤4:调用accept函数,从请求队列中获取一个连接 * 并返回新的socket描述符 * */ int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &len); if(fd < 0){ syslog(LOG_DEBUG, "accept error: %s\n", strerror(errno)); continue; } //输出客户端信息 out_addr(&clientAddr); //将返回的新socket描述符加入到动态数组中 add_fd(vfd, fd); } close(sockfd); destroy_vector_fd(vfd); return 0; }
//echo_tcp_client.c(与前面的例子相同)
#include <netdb.h> #include <sys/socket.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <memory.h> int main(int argc, char* argv[]) { if(argc < 3){ printf("usage: %s ip port\n", argv[0]); exit(1); } /*步骤1: 创建socket(套接字)*/ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0){ perror("socket error"); } //往servAddr中填入ip、port和地址族类型 struct sockaddr_in servAddr; memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_port = htons(atoi(argv[2])); //将ip地址转换成网络字节序后填入servAdd中 inet_pton(AF_INET, argv[1], &servAddr.sin_addr.s_addr); /*步骤2: 客户端调用connect函数连接到服务器端*/ if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){ perror("connect error"); exit(1); } /*步骤3: 调用自定义的协议处理函数和服务端进行双向通信*/ char buff[512]; size_t size; char* prompt = ">"; while(1){ memset(buff, 0, sizeof(buff)); write(STDOUT_FILENO, prompt, 1); size = read(STDIN_FILENO, buff, sizeof(buff)); if(size < 0) continue; buff[size-1] = '\0'; //将键盘输入的内容发送到服务端 if(write(sockfd, buff, sizeof(buff)) < 0){ perror("write error"); continue; }else{ memset(buff, 0, sizeof(buff)); //读取来自服务端的消息 if(read(sockfd, buff, sizeof(buff)) < 0){ perror("read error"); continue; }else{ printf("%s\n", buff); } } } /*关闭套接字*/ close(sockfd); }