• 第15章 高并发服务器编程(4)_守护进程(完结)


    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!
    ", 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
    ");
    
            //将fd从动态数组中删除
            remove_fd(vfd, fd);
            close(fd);
        }else if(size > 0){
            syslog(LOG_DEBUG, "%s
    ", buff); //显示客户端发送的消息
            //写回客户端(回显功能)
            if(write(fd, buff, sizeof(buff)) != size){
                if(errno == EPIPE){
                    //如果客户端己被关闭(相当于管道的读端关闭),会产生SIGPIPE信号
                    //并将errno设置为EPIPE
                    syslog(LOG_DEBUG, "write error
    ");
                    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
    ", 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
    ", strerror(errno));
            exit(1);
        }
    
        /*步骤3:调用listen函数启动监听
         *       通知系统去接受来自客户端的连接请求
         */
        if(listen(sockfd, 10) < 0){  //队列中最多允许10个连接请求
            //将日志信息写入到系统日志文件中(/var/log/syslog)
            syslog(LOG_DEBUG, "listen: %s
    ", 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
    ", 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
    ", 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
    ", 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] = '';
            //将键盘输入的内容发送到服务端
            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
    ", buff);
                }
            }
        }
    
        /*关闭套接字*/
        close(sockfd);
    }
  • 相关阅读:
    2017年10月9日 冒泡&去重复习
    2017 年9月29日 弹出层特效
    2017 年9月28日 三级联动
    2017 年 9 月 27 日 js(文本框内容添加到select)
    2017 年 9 月 27 日 js(1.两个select 内容互换 2.单选按钮 同意可点击下一步 3. 全选框)
    2017 年 9 月26 日
    linux运维的认知及RHEL7 Unix/Linux 系统 介绍和安装
    Zabbix配置文件详解之服务端zabbix_server
    LoadRunner安装+汉化+破解
    zabbix告警“Zabbix poller processes more than 75% busy”
  • 原文地址:https://www.cnblogs.com/5iedu/p/6700058.html
Copyright © 2020-2023  润新知