201909

 epoll 是Linux内核中的一种可扩展IO事件处理机制,最早在 Linux 2.5.44内核中引入,可被用于代替POSIX select 和 poll 系统调用,并且在具有大量应用程序请求时能够获得较好的性能( 此时被监视的文件描述符数目非常大,与旧的 select 和 poll 系统调用完成操作所需 O(n) 不同, epoll能在O(1)时间内完成操作,所以性能相当高),epoll 与 FreeBSD的kqueue类似,都向用户空间提供了自己的文件描述符来进行操作。

/*!
* Email: [email protected]
* Auth: scictor
* Date: 2019-9-9
* File: epoll_reactor_threadpoll.cpp
* Class: epoll_reactor_threadpoll (if applicable)
* Brief:
* Note:
 */
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>


#include "threadPool.h"


static int thpool_keepalive = 1 ;   //线程池保持存活
pthread_mutex_t  mutex  = PTHREAD_MUTEX_INITIALIZER ;  //静态赋值法初始化互斥锁


thpool_t * thpool_init (int threadsN){
    thpool_t  *tp_p ;

    if (!threadsN || threadsN < 1){
        threadsN = 1 ;

    }

    tp_p =  (thpool_t *)malloc (sizeof (thpool_t)) ;
    if (tp_p == NULL){
            fprintf (stderr ,"thpool_init (): could not allocate memory for thread pool\n");
        return NULL ;
    }
    tp_p->threads = (pthread_t *)malloc (threadsN * sizeof (pthread_t));
    if (tp_p->threads == NULL){
        fprintf( stderr , "could not allocation memory for thread id\n");
        return NULL;
    }
    tp_p->threadsN = threadsN ;


    if (thpool_jobqueue_init (tp_p) == -1){
        fprintf (stderr ,"could not allocate memory for job queue\n");
        return NULL;
        }

    /*初始化信号*/
    tp_p->jobqueue->queueSem = (sem_t *)malloc (sizeof (sem_t));

    /*定位一个匿名信号量,第二个参数是1表示。这个信号量将在进程内的线程是共享的,第三个参数是信号量的初始值*/
    sem_init (tp_p->jobqueue->queueSem, 0 , 0 );

    int  t ;



    for (t = 0 ; t < threadsN ; t++){
        printf ("Create thread %d in pool\n", t);

        //第四个参数是传递给函数指针的一个参数,这个函数指针就是我们所说的线程指针
        if (pthread_create (&(tp_p->threads[t]) , NULL , (void *) thpool_thread_do , (void *)tp_p)){
            free (tp_p->threads);

            free (tp_p->jobqueue->queueSem);
            free (tp_p->jobqueue);
            free (tp_p);
        }
    }
    return  tp_p ;
}



/*
 * 初始化完线程应该处理的事情
 * 这里存在两个信号量,
 */

void thpool_thread_do (thpool_t *tp_p){
    while (thpool_keepalive)
    {
        if (sem_wait (tp_p->jobqueue->queueSem))  //如果工作队列中没有工作,那么所有的线程都将在这里阻塞,当他调用成功的时候,信号量-1
        {
            fprintf(stderr , "Waiting for semaphore\n");
            exit (1);
        }

        if (thpool_keepalive)
        {
            void *(*func_buff) (void *arg);
            void *arg_buff;
            thpool_job_t *job_p;

            pthread_mutex_lock (&mutex);
            job_p = thpool_jobqueue_peek (tp_p);
                func_buff = job_p->function ;
            arg_buff= job_p->arg ;
            thpool_jobqueue_removelast (tp_p);
            pthread_mutex_unlock (&mutex);

            func_buff (arg_buff);

            free (job_p);
        }
        else
        {
            return ;
        }
    }
    return ;




}


int thpool_add_work (thpool_t *tp_p ,void * (*function_p )(void *), void *arg_p){

    thpool_job_t   *newjob ;

    newjob = (thpool_job_t *)malloc (sizeof (thpool_job_t));
    if (newjob == NULL)
    {
        fprintf (stderr,"couldnot allocate memory for new job\n");
        exit (1);
    }
    newjob->function = function_p ;
    newjob->arg = arg_p ;

    pthread_mutex_lock (&mutex);
    thpool_jobqueue_add (tp_p ,newjob);
    pthread_mutex_unlock (&mutex);
    return 0 ;
}


void thpool_destory (thpool_t *tp_p){
    int    t ;

    thpool_keepalive = 0 ;  //让所有的线程运行的线程都退出循环

    for (t = 0 ; t < (tp_p->threadsN) ; t++ ){

        //sem_post 会使在这个线程上阻塞的线程,不再阻塞
        if (sem_post (tp_p->jobqueue->queueSem) ){
            fprintf (stderr,"thpool_destory () : could not bypass sem_wait ()\n");
        }

    }
    if (sem_destroy (tp_p->jobqueue->queueSem)!= 0){
        fprintf (stderr, "thpool_destory () : could not destroy semaphore\n");
    }

    for (t = 0 ; t< (tp_p->threadsN) ; t++)
    {
        pthread_join (tp_p->threads[t], NULL);
    }
    thpool_jobqueue_empty (tp_p);
    free (tp_p->threads);
    free (tp_p->jobqueue->queueSem);
    free (tp_p->jobqueue);
    free (tp_p);



}


int thpool_jobqueue_init (thpool_t *tp_p)
{
    tp_p->jobqueue = (thpool_jobqueue *)malloc (sizeof (thpool_jobqueue));
    if (tp_p->jobqueue == NULL)
    {
        fprintf (stderr ,"thpool_jobqueue malloc is error\n");
        return -1 ;
    }
    tp_p->jobqueue->tail = NULL ;
    tp_p->jobqueue->head = NULL ;
    tp_p->jobqueue->jobsN = 0 ;
    return 0 ;

}

void thpool_jobqueue_add (thpool_t *tp_p , thpool_job_t *newjob_p){
    newjob_p->next = NULL ;
    newjob_p->prev = NULL ;

    thpool_job_t   *oldfirstjob ;
    oldfirstjob = tp_p->jobqueue->head;


    switch (tp_p->jobqueue->jobsN)
    {
        case 0 :
             tp_p->jobqueue->tail = newjob_p;
             tp_p->jobqueue->head = newjob_p;
             break;
        default :
             oldfirstjob->prev= newjob_p ;
             newjob_p->next = oldfirstjob ;
             tp_p->jobqueue->head= newjob_p;
             break;
    }

        (tp_p->jobqueue->jobsN)++ ;
    sem_post (tp_p->jobqueue->queueSem);  //原子操作,信号量增加1 ,保证线程安全

    int sval ;
    sem_getvalue (tp_p->jobqueue->queueSem , &sval);   //sval表示当前正在阻塞的线程数量

}

int thpool_jobqueue_removelast (thpool_t *tp_p){
    thpool_job_t *oldlastjob  , *tmp;
    oldlastjob = tp_p->jobqueue->tail ;


    switch (tp_p->jobqueue->jobsN)
    {
        case 0 :
            return -1 ;
            break;
        case 1 :
            tp_p->jobqueue->head = NULL ;
            tp_p->jobqueue->tail = NULL ;
            break;
        default :
            tmp = oldlastjob->prev ;
            tmp->next = NULL ;
            tp_p->jobqueue->tail = oldlastjob->prev;

    }
    (tp_p->jobqueue->jobsN) -- ;
    int sval ;
    sem_getvalue (tp_p->jobqueue->queueSem, &sval);
    return 0 ;
}
thpool_job_t * thpool_jobqueue_peek (thpool_t *tp_p){
    return tp_p->jobqueue->tail ;
}


void thpool_jobqueue_empty (thpool_t *tp_p)
{
    thpool_job_t *curjob;
    curjob = tp_p->jobqueue->tail ;
    while (tp_p->jobqueue->jobsN){
        tp_p->jobqueue->tail = curjob->prev ;
        free (curjob);
        curjob = tp_p->jobqueue->tail ;
        tp_p->jobqueue->jobsN -- ;
    }
    tp_p->jobqueue->tail = NULL ;
    tp_p->jobqueue->head = NULL ;
}
/*!
* Email: [email protected]
* Auth: scictor
* Date: 2019-9-9
* File: epoll_reactor_threadpoll.h
* Class: epoll_reactor_threadpoll (if applicable)
* Brief:
* Note:
 */
#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include <pthread.h>
#include <semaphore.h>

/*Individual job*/
typedef struct thpool_job_t {
    void   (*function)(void* arg);    //函数指针
    void                    *arg ;    //函数的参数
    struct tpool_job_t     *next ;    //指向下一个任务
    struct tpool_job_t     *prev ;    //指向前一个任务
}thpool_job_t ;

/*job queue as doubly linked list*/
typedef struct thpool_jobqueue {
    thpool_job_t    *head ;           //队列的头指针
    thpool_job_t    *tail;            //对列的尾指针
    int             jobsN;           //队列中工作的个数
    sem_t           *queueSem;        //原子信号量
}thpool_jobqueue;

/*thread pool*/

typedef struct thpool_t {
    pthread_t          *threads ;        //线程的ID
    int                 threadsN ;        //线程的数量
    thpool_jobqueue    *jobqueue;        //工作队列的指针


}thpool_t;


/*线程池中的线程都需要互斥锁和指向线程池的一个指针*/
typedef struct thread_data{
    pthread_mutex_t     *mutex_p ;
    thpool_t            *tp_p ;
}thread_data;



/*
 *    初始化线程池
 *    为线程池, 工作队列, 申请内存空间,信号等申请内存空间
 *    @param  :将被使用的线程ID
 *    @return :成功返回的线程池结构体,错误返回null
 */

thpool_t   *thpool_init (int threadsN);

/*
 * 每个线程要做的事情
 * 这是一个无止境循环,当撤销这线程池的时候,这个循环才会被中断
 *@param: 线程池
 *@return:不做任何的事情
 */

void  thpool_thread_do (thpool_t *tp_p);

/*
 *向工作队列里面添加任何
 *采用来了一个行为和他的参数,添加到线程池的工作对列中去,
 * 如果你想添加工作函数,需要更多的参数,通过传递一个指向结构体的指针,就可以实现一个接口
 * ATTENTION:为了不引起警告,你不得不将函数和参数都带上
 *
 * @param: 添加工作的线程线程池
 * @param: 这个工作的处理函数
 * @param:函数的参数
 * @return : int
 */

int thpool_t_add_work (thpool_t *tp_p ,void* (*function_p) (void *), void* arg_p );


/*
 *摧毁线程池
 *
 *这将撤销这个线程池和释放所申请的内存空间,当你在调用这个函数的时候,存在有的线程还在运行中,那么
 *停止他们现在所做的工作,然后他们被撤销掉
 * @param:你想要撤销的线程池的指针
 */


void thpool_destory (thpool_t  *tp_p);

/*-----------------------Queue specific---------------------------------*/



/*
 * 初始化队列
 * @param: 指向线程池的指针
 * @return :成功的时候返回是 0 ,分配内存失败的时候,返回是-1
 */
int thpool_jobqueue_init (thpool_t *tp_p);


/*
 *添加任务到队列
 *一个新的工作任务将被添加到队列,在使用这个函数或者其他向别的类似这样
 *函数 thpool_jobqueue_empty ()之前,这个新的任务要被申请内存空间
 *
 * @param: 指向线程池的指针
 * @param:指向一个已经申请内存空间的任务
 * @return   nothing
 */
void thpool_jobqueue_add (thpool_t * tp_p , thpool_job_t *newjob_p);

/*
 * 移除对列的最后一个任务
 *这个函数将不会被释放申请的内存空间,所以要保证
 *
 *@param :指向线程池的指针
 *@return : 成功返回0 ,如果对列是空的,就返回-1
 */
int thpool_jobqueue_removelast (thpool_t *tp_p);


/*
 *对列的最后一个任务
 *在队列里面得到最后一个任务,即使队列是空的,这个函数依旧可以使用
 *
 *参数:指向线程池结构体的指针
 *返回值:得到队列中最后一个任务的指针,或者在对列是空的情况下,返回是空
 */
thpool_job_t * thpool_jobqueue_peek (thpool_t *tp_p);

/*
 *移除和撤销这个队列中的所有任务
 *这个函数将删除这个队列中的所有任务,将任务对列恢复到初始化状态,因此队列的头和对列的尾都设置为NULL ,此时队列中任务= 0
 *
 *参数:指向线程池结构体的指针
 *
 */
void thpool_jobqueue_empty (thpool_t *tp_p);

#endif
/*!
* Email: [email protected]
* Auth: scictor
* Date: 2019-9-9
* File: epoll_reactor_main.cpp
* Class: %{Cpp:License:ClassName} (if applicable)
* Brief:
* Note:
 */
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <pthread.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include "threadPool.h"

#define MAX_EVENT_NUMBER  1000
#define SIZE    1024
#define MAX     10

//从主线程向工作线程数据结构
struct fd
{
    int epollfd;
    int sockfd ;
};

//用户说明
struct user
{
    int  sockfd ;   //文件描述符
    char client_buf [SIZE]; //数据的缓冲区
};
struct user user_client[MAX];  //定义一个全局的客户数据表

/*
EPOLL事件有两种模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水平触发模式)是缺省的工作方式,并且同时支持 block 和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
ET(edge-triggered,边缘触发模式)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次出发就绪事件。
*/
//由于epoll设置的EPOLLONESHOT模式,当出现errno =EAGAIN,就需要重新设置文件描述符(可读)
void reset_oneshot (int epollfd , int fd)
{
    struct epoll_event event ;
    event.data.fd = fd ;
    /*
EPOLLONESHOT:
epoll有两种触发的方式即LT(水平触发)和ET(边缘触发)两种,在前者,只要存在着事件就会不断的触发,直到处理完成,而后者只触发一次相同事件或者说只在从非触发到触发两个状态转换的时候儿才触发。
这会出现下面一种情况,如果是多线程在处理,一个SOCKET事件到来,数据开始解析,这时候这个SOCKET又来了同样一个这样的事件,而你的数据解析尚未完成,那么程序会自动调度另外一个线程或者进程来处理新的事件,这造成一个很严重的问题,不同的线程或者进程在处理同一个SOCKET的事件,这会使程序的健壮性大降低而编程的复杂度大大增加!!即使在ET模式下也有可能出现这种情况!!
解决这种现象有两种方法,一种是在单独的线程或进程里解析数据,也就是说,接收数据的线程接收到数据后立刻将数据转移至另外的线程。
第二种方法就是本文要提到的EPOLLONESHOT这种方法,可以在epoll上注册这个事件,注册这个事件后,如果在处理写成当前的SOCKET后不再重新注册相关事件,那么这个事件就不再响应了或者说触发了。要想重新注册事件则需要调用epoll_ctl重置文件描述符上的事件,这样前面的socket就不会出现竞态这样就可以通过手动的方式来保证同一SOCKET只能被一个线程处理,不会跨越多个线程。

events 可以是以下几个宏的集合:
EPOLLIN     //表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT    //表示对应的文件描述符可以写;
EPOLLPRI    //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR    //表示对应的文件描述符发生错误;
EPOLLHUP    //表示对应的文件描述符被挂断;
EPOLLET     //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT//只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

EPOLLIN事件:
当对方关闭连接(FIN), EPOLLERR,都可以认为是一种EPOLLIN事件,在read的时候分别有0,-1两个返回值。
*/
    event.events = EPOLLIN|EPOLLET|EPOLLONESHOT ;
    epoll_ctl (epollfd , EPOLL_CTL_MOD, fd , &event);

}
//向epoll内核事件表里面添加可写的事件
int addreadfd (int epollfd , int fd , int oneshot)
{
    struct epoll_event  event;
    event.data.fd = fd ;
    event.events |= ~ EPOLLIN ;
    event.events |= EPOLLOUT ;
    event.events |= EPOLLET;
    if (oneshot)
    {
        event.events |= EPOLLONESHOT ; //设置EPOLLONESHOT

    }
    /*
EPOLL_CTL_ADD    //注册新的fd到epfd中;
EPOLL_CTL_MOD    //修改已经注册的fd的监听事件;
EPOLL_CTL_DEL    //从epfd中删除一个fd;
*/
    epoll_ctl (epollfd , EPOLL_CTL_MOD ,fd , &event);

}
//群聊函数
int groupchat (int epollfd , int sockfd , char *buf)
{

    int i = 0 ;
    for ( i  = 0 ; i < MAX ; i++)
    {
        if (user_client[i].sockfd == sockfd)
        {
            continue ;
        }
        strncpy (user_client[i].client_buf ,buf , strlen (buf)) ;
        addreadfd (epollfd , user_client[i].sockfd , 1);

    }

}
//接受数据的函数,也就是线程的回调函数
int funcation (void *args)
{
    int sockfd = ((struct fd*)args)->sockfd ;
    int epollfd =((struct fd*)args)->epollfd;
    char buf[SIZE];
    memset (buf , '\0', SIZE);

    printf ("start new thread to receive data on fd :%d\n", sockfd);

    //由于我将epoll的工作模式设置为ET模式,所以就要用一个循环来读取数据,防止数据没有读完,而丢失。
    while (1)
    {
        int ret = recv (sockfd ,buf , SIZE-1 , 0);
        if (ret == 0)
        {
            close (sockfd);
            break;
        }
        else if (ret < 0)
        {
            if (errno == EAGAIN)
            {
                reset_oneshot (epollfd, sockfd);  //重新设置(上面已经解释了)
                break;
            }
        }
        else
        {
            printf (" read data is %s\n", buf);
            sleep (5);
            groupchat (epollfd , sockfd, buf );
        }


    }
    printf ("end thread receive  data on fd : %d\n", sockfd);

}
//这是重新注册,将文件描述符从可写变成可读
int addagainfd (int epollfd , int fd)
{
    struct epoll_event event;
    event.data.fd = fd ;
    event.events  |= ~EPOLLOUT ;
    event.events = EPOLLIN|EPOLLET|EPOLLONESHOT;
    epoll_ctl (epollfd , EPOLL_CTL_MOD , fd , &event);
}
//与前面的解释一样
int reset_read_oneshot (int epollfd , int sockfd)
{
    struct epoll_event  event;
    event.data.fd = sockfd ;
    event.events = EPOLLOUT |EPOLLET |EPOLLONESHOT ;
    epoll_ctl (epollfd, EPOLL_CTL_MOD , sockfd , &event);
    return 0 ;

}

//发送读的数据
int readfun (void *args)
{
    int sockfd = ((struct fd *)args)->sockfd ;
    int epollfd= ((struct fd*)args)->epollfd ;

    int ret = send (sockfd, user_client[sockfd].client_buf , strlen (user_client[sockfd].client_buf), 0); //发送数据
    if (ret == 0 )
    {

        close (sockfd);
        printf ("发送数据失败\n");
        return -1 ;
    }
    else if (ret == EAGAIN)
    {
        reset_read_oneshot (epollfd , sockfd);
        printf("send later\n");
        return -1;
    }
    memset (&user_client[sockfd].client_buf , '\0', sizeof (user_client[sockfd].client_buf));
    addagainfd (epollfd , sockfd);//重新设置文件描述符

}
//套接字设置为非阻塞
int setnoblocking (int fd)
{
    int old_option = fcntl (fd, F_GETFL);
    int new_option = old_option|O_NONBLOCK;
    fcntl (fd , F_SETFL , new_option);
    return old_option ;
}

int addfd (int epollfd , int fd , int oneshot)
{
    struct epoll_event  event;
    event.data.fd = fd ;
    event.events = EPOLLIN|EPOLLET ;
    if (oneshot)
    {
        event.events |= EPOLLONESHOT ;

    }
    epoll_ctl (epollfd , EPOLL_CTL_ADD ,fd ,  &event);
    setnoblocking (fd);
    return 0 ;
}



int main(int argc, char *argv[])
{
    struct sockaddr_in  address ;
    const char *ip = "127.0.0.1";
    int port  = 8086 ;

    memset (&address , 0 , sizeof (address));
    address.sin_family = AF_INET ;
    inet_pton (AF_INET ,ip , &address.sin_addr);
    address.sin_port =htons( port) ;


    int listenfd = socket (AF_INET, SOCK_STREAM, 0);
    assert (listen >=0);
    int reuse = 1;
    setsockopt (listenfd , SOL_SOCKET , SO_REUSEADDR , &reuse , sizeof (reuse)); //端口重用,因为出现过端口无法绑定的错误
    int ret = bind (listenfd, (struct sockaddr*)&address , sizeof (address));
    assert (ret >=0 );

    ret = listen (listenfd , 5);
    assert (ret >=0);


    struct epoll_event events[MAX_EVENT_NUMBER];

    int epollfd = epoll_create (5); //创建内核事件描述符表
    assert (epollfd != -1);
    addfd (epollfd , listenfd, 0);

    thpool_t  *thpool ;  //线程池
    thpool = thpool_init (5) ; //线程池的一个初始化

    while (1)
    {
        int ret = epoll_wait (epollfd, events, MAX_EVENT_NUMBER , -1);//等待就绪的文件描述符,这个函数会将就绪的复制到events的结构体数组中。
        if (ret < 0)
        {
            printf ("poll failure\n");
            break ;
        }
        int i =0  ;
        for ( i = 0 ; i < ret ; i++ )
        {
            int sockfd = events[i].data.fd ;

            if (sockfd == listenfd)
            {
                struct sockaddr_in client_address ;
                socklen_t  client_length = sizeof (client_address);
                int connfd = accept (listenfd , (struct sockaddr*)&client_address,&client_length);
                user_client[connfd].sockfd = connfd ;
                memset (&user_client[connfd].client_buf , '\0', sizeof (user_client[connfd].client_buf));
                addfd (epollfd , connfd , 1);//将新的套接字加入到内核事件表里面。
            }
            else if (events[i].events & EPOLLIN)
            {
                struct fd    fds_for_new_worker ;
                fds_for_new_worker.epollfd = epollfd ;
                fds_for_new_worker.sockfd = sockfd ;

                thpool_add_work (thpool, (void*)funcation ,&fds_for_new_worker);//将任务添加到工作队列中
            }else if (events[i].events & EPOLLOUT)
            {

                struct  fd   fds_for_new_worker ;
                fds_for_new_worker.epollfd = epollfd ;
                fds_for_new_worker.sockfd = sockfd ;
                thpool_add_work (thpool, (void*)readfun , &fds_for_new_worker );//将任务添加到工作队列中
            }

        }

    }

    thpool_destory (thpool);
    close (listenfd);
    return EXIT_SUCCESS;
}

猜你喜欢

转载自www.cnblogs.com/guxuanqing/p/11489542.html