六十一、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

  1 #include <netdb.h>
  2 #include <netinet/in.h>
  3 #include <sys/socket.h>
  4 #include <sys/wait.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 #include <stdio.h>
  8 #include <stdlib.h>
  9 #include <memory.h>
 10 #include <signal.h>
 11 #include <fcntl.h>
 12 #include <time.h>
 13 #include <arpa/inet.h>
 14 #include <errno.h>
 15 #include <pthread.h>
 16 #include <syslog.h>
 17 #include <sys/types.h>
 18 #include <sys/stat.h>
 19 #include "vector_fd.h"
 20 
 21 vector_fd *vfd;
 22 int sockfd;
 23 
 24 /**
 25  * fd 对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
 26  */
 27 void do_service(int fd)
 28 {
 29     char buff[512];
 30     memset(buff, 0, sizeof(buff));
 31 
 32     /**
 33      *  因为采用非阻塞方式,若读不到数据直接返回,
 34      *  直接服务于下一个客户端,
 35      *  因此不需要判断 size < 0 的情况 */
 36     ssize_t size = read(fd, buff, sizeof(buff));
 37 
 38     if(size == 0){
 39         /** 客户端已经关闭连接 */
 40         syslog(LOG_DEBUG, "client closed");
 41         /** 从动态数组中删除对应的 fd */
 42         remove_fd(vfd, fd);
 43         /** 关闭对应客户端的 socket */
 44         close(fd);
 45     }
 46     else if(size > 0){
 47         syslog(LOG_DEBUG, "%s", buff);
 48         if(write(fd, buff, size) < 0){
 49             if(errno == EPIPE){
 50                 /** 客户端关闭连接 */
 51                 syslog(LOG_DEBUG, "write error:%s\n", strerror(errno));
 52                 remove_fd(vfd, fd);
 53                 close(fd);
 54             }
 55         }
 56     }
 57 }
 58 
 59 void out_addr(struct sockaddr_in *clientaddr)
 60 {
 61     char ip[16];
 62     memset(ip, 0, sizeof(ip));
 63     int port = ntohs(clientaddr->sin_port);
 64     inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
 65     syslog(LOG_DEBUG, "%s(%d) connected!\n", ip, port);
 66 }
 67 
 68 /** 遍历出动态数组中所有的描述符并加入到描述符集 set
 69  * 中,同时此函数返回动态数组中最大的那个描述符 */
 70 int add_set(fd_set *set)
 71 {
 72     FD_ZERO(set);   ///< 清空描述符集
 73     int max_fd = vfd->fd[0];
 74     int i = 0;
 75     for(; i < vfd->counter; i++){
 76         int fd = get_fd(vfd, i);
 77         if(fd > max_fd) max_fd = fd;
 78         FD_SET(fd, set);    ///< 将 fd 加入到描述符集中
 79     }
 80 
 81     return max_fd;
 82 }
 83 
 84 void *th_fn(void *arg)
 85 {
 86     /** 设置超时时间 2s */
 87     struct timeval t;
 88     t.tv_sec = 2;
 89     t.tv_usec = 0;
 90 
 91     int n = 0;
 92     int maxfd;
 93     fd_set set; ///< 描述符集
 94     maxfd = add_set(&set);
 95 
 96     /**
 97      * 调用 select 函数会阻塞,委托内核去检查传入的描述符是否准备好,
 98      * 若有则返回准备好的描述符;超时则返回 0
 99      * 第一个参数为描述符集中的描述符的范围(最大描述符 + 1)
100      */
101     while((n = select(maxfd + 1, &set, NULL, NULL, &t)) >= 0){
102         /** 检测哪些描述符准备好, 并和这些准备好的描述符对应的客户端进行数据的双向通信 */
103         if(n > 0){
104             int i = 0;
105             for(; i < vfd->counter; i++){
106                 int fd = get_fd(vfd, i);
107                 if(FD_ISSET(fd, &set)){
108                     do_service(fd);
109                 }
110             }
111         }
112         /** 重新设置时间和清空描述符集 */
113         t.tv_sec = 2;
114         t.tv_usec = 0;
115         /** 重新遍历动态数组中最新的描述符放置到描述符集中 */
116         maxfd = add_set(&set);
117     }
118 
119     return (void *)0;
120 }
121 
122 int main(int argc, char *argv[])
123 {
124     if(argc < 2){
125         printf("usage: %s #port\n", argv[0]);
126         exit(1);
127     }
128 
129     /** 守护进程编程的5个步骤 */
130     /** 步骤1: 创建屏蔽字为 0 */
131     umask(0);
132     /** 步骤2: 调用 fork 函数创建子进程,然后父进程退出 */
133     pid_t pid = fork();
134     if(pid > 0) exit(0);
135     /** 步骤3: 调用 setsid 函数创建一个新会话 */
136     setsid();
137     /** 步骤4: 将当前工作目录更改为更目录 */
138     chdir("/");
139     /** 步骤5: 关闭不需要的文件描述符 */
140     close(STDIN_FILENO);
141     close(STDOUT_FILENO);
142     close(STDERR_FILENO);
143 
144     /** 打开系统日志服务的一个连接 */
145     openlog(argv[0], LOG_PID, LOG_SYSLOG);
146 
147 
148     /** 步骤1: 创建 socket(套接字)
149      *  注: socket 创建在内核中,是一个结构体.
150      *  AF_INET: IPV4
151      *  SOCK_STREAM: tcp 协议
152      *  AF_INET6: IPV6
153      */
154     sockfd = socket(AF_INET, SOCK_STREAM, 0);
155     if(sockfd < 0){
156         /** 将日志信息写入到系统日志文件中(/var/log/syslog) */
157         syslog(LOG_DEBUG, "socket:%s\n", strerror(errno));
158         exit(1);
159     }
160 
161     /**
162      * 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
163      */
164     struct sockaddr_in  serveraddr;
165     memset(&serveraddr, 0, sizeof(struct sockaddr_in));
166     /** 往地址中填入 ip、port、internet 地址族类型 */
167     serveraddr.sin_family = AF_INET;    ///< IPV4
168     serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
169     serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
170     if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
171         syslog(LOG_DEBUG, "bind:%s\n", strerror(errno));
172         exit(1);
173     }
174 
175     /**
176      *  步骤3: 调用 listen 函数启动监听(指定 port 监听)
177      *         通知系统去接受来自客户端的连接请求
178      *         (将接受到的客户端连接请求放置到对应的队列中)
179      *  第二个参数: 指定队列的长度
180      */
181     if(listen(sockfd, 10) < 0){
182         syslog(LOG_DEBUG, "listen:%s\n", strerror(errno));
183         exit(1);
184     }
185 
186     /** 创建放置套接字描述符 fd 的动态数组 */
187     vfd = create_vector_fd();
188 
189     /** 设置线程的分离属性 */
190     pthread_t th;
191     pthread_attr_t attr;
192     pthread_attr_init(&attr);
193     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
194     int err;
195     if((err = pthread_create(&th, &attr, th_fn, (void *)0)) != 0){
196         syslog(LOG_DEBUG, "pthread create:%s\n", strerror(errno));
197         exit(1);
198     }
199     pthread_attr_destroy(&attr);
200 
201     /**
202      * 1)主控线程获得客户端的链接,将新的 socket 描述符放置到动态数组中
203      * 2) (a)启动的子线程调用 select 函数委托内核去检查传入到 select
204      *       中的描述符是否准备好.
205      *    (b)利用 FD_ISSET 来找出准备好的那些描述符,
206      *       并和对应的客户端进行双向通信(非阻塞)
207      */
208     struct sockaddr_in clientaddr;
209     socklen_t len = sizeof(clientaddr);
210 
211     while(1){
212         /**
213          *  步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
214          *         socket 描述符
215          *  注意:  若没有客户端连接,调用此函数后会阻塞, 直到获得一个客户端的连接
216          */
217         /** 主控线程负责调用 accept 去获得客户端的连接 */
218         int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
219         if(fd < 0){
220             syslog(LOG_DEBUG, "accept:%s\n", strerror(errno));
221             continue;
222         }
223 
224         out_addr(&clientaddr);
225 
226         /** 将返回的新的 socket 描述符加入到动态数组中 */
227         add_fd(vfd, fd);
228     }
229     closelog();
230 
231     return 0;
232 }

  编译运行如下:

  查看 /var/log/syslog/

  

 

posted @ 2019-03-13 22:42  游戏进行中  阅读(304)  评论(0编辑  收藏  举报