浅墨浓香

想要天亮进城,就得天黑赶路。

导航

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

Posted on 2017-04-12 16:51  浅墨浓香  阅读(467)  评论(0编辑  收藏  举报

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