epoll
是Linux内核中的一种可扩展IO事件处理机制,最早在 Linux 2.5.44内核中引入,可被用于代替POSIX select 和 poll 系统调用,并且在具有大量应用程序请求时能够获得较好的性能( 此时被监视的文件描述符数目非常大,与旧的 select 和 poll 系统调用完成操作所需 O(n) 不同, epoll能在O(1)时间内完成操作,所以性能相当高),epoll 与 FreeBSD的kqueue类似,都向用户空间提供了自己的文件描述符来进行操作。
IO模型实现reactor 模式的工作流程:
(1)主线程向epoll内核事件表内注册socket上的可读就绪事件。
(2)主线程调用epoll_wait()等待socket上有数据可读
(3)当socket上有数据可读,epoll_wait 通知主线程。主线程从socket可读事件放入请求队列。
(4)睡眠在请求队列上的某个可读工作线程被唤醒,从socket上读取数据,处理客户的请求。
然后向 epoll内核事件表里注册写的就绪事件
(5)主线程调用epoll_wait()等待数据可写 。
1 /*! 2 * Email: scictor@gmail.com 3 * Auth: scictor 4 * Date: 2019-9-9 5 * File: epoll_reactor_threadpoll.h 6 * Class: epoll_reactor_threadpoll (if applicable) 7 * Brief: 8 * Note: 9 */ 10 #ifndef THREAD_POOL_H 11 #define THREAD_POOL_H 12 13 #include <pthread.h> 14 #include <semaphore.h> 15 16 typedef void *(*func_call)(void*); 17 18 /*Individual job*/ 19 typedef struct thpool_job_t { 20 // void (*function)(void* arg); //函数指针 21 void *(*function)(void* arg); //函数指针 22 void *arg ; //函数的参数 23 /*struct tpool_job_t *next ; //指向下一个任务 24 struct tpool_job_t *prev ; //指向前一个任务*/ 25 struct thpool_job_t *next ; //指向下一个任务 26 struct thpool_job_t *prev ; //指向前一个任务 27 }thpool_job_t ; 28 29 /*job queue as doubly linked list双向链表*/ 30 typedef struct thpool_jobqueue { 31 thpool_job_t *head ; //队列的头指针(头部添加job任务) 32 thpool_job_t *tail; //对列的尾指针(尾部做任务,并移除) 33 int jobsN; //队列中工作的个数 34 sem_t *queueSem; //原子信号量 35 }thpool_jobqueue; 36 37 /*thread pool*/ 38 39 typedef struct thpool_t { 40 pthread_t *threads ; //线程的ID 41 int threadsN ; //线程的数量 42 thpool_jobqueue *jobqueue; //工作队列的指针 43 44 45 }thpool_t; 46 47 48 /*线程池中的线程都需要互斥锁和指向线程池的一个指针*/ 49 typedef struct thread_data{ 50 pthread_mutex_t *mutex_p ; 51 thpool_t *tp_p ; 52 }thread_data; 53 54 55 56 /* 57 * 初始化线程池 58 * 为线程池, 工作队列, 申请内存空间,信号等申请内存空间 59 * @param :将被使用的线程ID 60 * @return :成功返回的线程池结构体,错误返回null 61 */ 62 63 thpool_t *thpool_init (int threadsN); 64 65 /* 66 * 每个线程要做的事情 67 * 这是一个无止境循环,当撤销这线程池的时候,这个循环才会被中断 68 *@param: 线程池 69 *@return:不做任何的事情 70 */ 71 72 void thpool_thread_do (thpool_t *tp_p); 73 74 /* 75 *向工作队列里面添加任何 76 *采用来了一个行为和他的参数,添加到线程池的工作对列中去, 77 * 如果你想添加工作函数,需要更多的参数,通过传递一个指向结构体的指针,就可以实现一个接口 78 * ATTENTION:为了不引起警告,你不得不将函数和参数都带上 79 * 80 * @param: 添加工作的线程线程池 81 * @param: 这个工作的处理函数 82 * @param:函数的参数 83 * @return : int 84 */ 85 86 int thpool_add_work (thpool_t *tp_p ,void* (*function_p) (void *), void* arg_p ); 87 88 89 /* 90 *摧毁线程池 91 * 92 *这将撤销这个线程池和释放所申请的内存空间,当你在调用这个函数的时候,存在有的线程还在运行中,那么 93 *停止他们现在所做的工作,然后他们被撤销掉 94 * @param:你想要撤销的线程池的指针 95 */ 96 97 98 void thpool_destory (thpool_t *tp_p); 99 100 /*-----------------------Queue specific---------------------------------*/ 101 102 103 104 /* 105 * 初始化队列 106 * @param: 指向线程池的指针 107 * @return :成功的时候返回是 0 ,分配内存失败的时候,返回是-1 108 */ 109 int thpool_jobqueue_init (thpool_t *tp_p); 110 111 112 /* 113 *添加任务到队列 114 *一个新的工作任务将被添加到队列,在使用这个函数或者其他向别的类似这样 115 *函数 thpool_jobqueue_empty ()之前,这个新的任务要被申请内存空间 116 * 117 * @param: 指向线程池的指针 118 * @param:指向一个已经申请内存空间的任务 119 * @return nothing 120 */ 121 void thpool_jobqueue_add (thpool_t * tp_p , thpool_job_t *newjob_p); 122 123 /* 124 * 移除对列的最后一个任务 125 *这个函数将不会被释放申请的内存空间,所以要保证 126 * 127 *@param :指向线程池的指针 128 *@return : 成功返回0 ,如果对列是空的,就返回-1 129 */ 130 int thpool_jobqueue_removelast (thpool_t *tp_p); 131 132 133 /* 134 *对列的最后一个任务 135 *在队列里面得到最后一个任务,即使队列是空的,这个函数依旧可以使用 136 * 137 *参数:指向线程池结构体的指针 138 *返回值:得到队列中最后一个任务的指针,或者在对列是空的情况下,返回是空 139 */ 140 thpool_job_t * thpool_jobqueue_peek (thpool_t *tp_p); 141 142 /* 143 *移除和撤销这个队列中的所有任务 144 *这个函数将删除这个队列中的所有任务,将任务对列恢复到初始化状态,因此队列的头和对列的尾都设置为NULL ,此时队列中任务= 0 145 * 146 *参数:指向线程池结构体的指针 147 * 148 */ 149 void thpool_jobqueue_empty (thpool_t *tp_p); 150 151 #endif
1 /*! 2 * Email: scictor@gmail.com 3 * Auth: scictor 4 * Date: 2019-9-9 5 * File: epoll_reactor_threadpoll.cpp 6 * Class: epoll_reactor_threadpoll (if applicable) 7 * Brief: 8 * Note: 9 */ 10 #include <unistd.h> 11 #include <assert.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <errno.h> 15 #include <pthread.h> 16 #include <semaphore.h> 17 18 19 #include "threadPool.h" 20 21 22 static int thpool_keepalive = 1 ; //线程池保持存活 23 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ; //静态赋值法初始化互斥锁 24 25 26 thpool_t * thpool_init (int threadsN){ 27 thpool_t *tp_p ; 28 29 if (!threadsN || threadsN < 1){ 30 threadsN = 1 ; 31 32 } 33 34 tp_p = (thpool_t *)malloc (sizeof (thpool_t)) ; 35 if (tp_p == NULL){ 36 fprintf (stderr ,"thpool_init (): could not allocate memory for thread pool "); 37 return NULL ; 38 } 39 tp_p->threads = (pthread_t *)malloc (threadsN * sizeof (pthread_t)); 40 if (tp_p->threads == NULL){ 41 fprintf( stderr , "could not allocation memory for thread id "); 42 return NULL; 43 } 44 tp_p->threadsN = threadsN ; 45 46 47 if (thpool_jobqueue_init (tp_p) == -1){ 48 fprintf (stderr ,"could not allocate memory for job queue "); 49 return NULL; 50 } 51 52 /*初始化信号*/ 53 tp_p->jobqueue->queueSem = (sem_t *)malloc (sizeof (sem_t)); 54 55 /*定位一个匿名信号量,第二个参数是0表示。这个信号量将在进程内的线程是共享的,为1表示进程间共享,第三个参数是信号量的初始值*/ 56 sem_init (tp_p->jobqueue->queueSem, 0 , 0 ); 57 58 int t ; 59 60 61 62 for (t = 0 ; t < threadsN ; t++){ 63 printf ("Create thread %d in pool ", t); 64 65 //第四个参数是传递给函数指针的一个参数,这个函数指针就是我们所说的线程指针 66 if (pthread_create (&(tp_p->threads[t]) , NULL , (void *) thpool_thread_do , (void *)tp_p)){ 67 free (tp_p->threads); 68 69 free (tp_p->jobqueue->queueSem); 70 free (tp_p->jobqueue); 71 free (tp_p); 72 } 73 } 74 return tp_p ; 75 } 76 77 78 79 /* 80 * 初始化完线程应该处理的事情 81 * 这里存在两个信号量, 82 */ 83 84 void thpool_thread_do (thpool_t *tp_p){ 85 while (thpool_keepalive) 86 { 87 if (sem_wait (tp_p->jobqueue->queueSem)) //如果工作队列中没有工作,那么所有的线程都将在这里阻塞,当他调用成功的时候,信号量-1 88 { 89 fprintf(stderr , "Waiting for semaphore "); 90 exit (1); 91 } 92 93 if (thpool_keepalive) 94 { 95 void *(*func_buff) (void *arg); 96 void *arg_buff; 97 thpool_job_t *job_p; 98 99 pthread_mutex_lock (&mutex); 100 job_p = thpool_jobqueue_peek (tp_p); 101 func_buff = job_p->function ; 102 arg_buff= job_p->arg ; 103 thpool_jobqueue_removelast (tp_p); 104 pthread_mutex_unlock (&mutex); 105 106 func_buff (arg_buff); 107 108 free (job_p); 109 } 110 else 111 { 112 return ; 113 } 114 } 115 return ; 116 117 118 119 120 } 121 122 123 int thpool_add_work (thpool_t *tp_p ,void * (*function_p )(void *), void *arg_p){ 124 125 thpool_job_t *newjob ; 126 127 newjob = (thpool_job_t *)malloc (sizeof (thpool_job_t)); 128 if (newjob == NULL) 129 { 130 fprintf (stderr,"couldnot allocate memory for new job "); 131 exit (1); 132 } 133 newjob->function = function_p ; 134 newjob->arg = arg_p ; 135 136 pthread_mutex_lock (&mutex); 137 thpool_jobqueue_add (tp_p ,newjob); 138 pthread_mutex_unlock (&mutex); 139 return 0 ; 140 } 141 142 143 void thpool_destory (thpool_t *tp_p){ 144 int t ; 145 146 thpool_keepalive = 0 ; //让所有的线程运行的线程都退出循环 147 148 for (t = 0 ; t < (tp_p->threadsN) ; t++ ){ 149 150 //sem_post 会使在这个线程上阻塞的线程,不再阻塞 151 if (sem_post (tp_p->jobqueue->queueSem) ){ 152 fprintf (stderr,"thpool_destory () : could not bypass sem_wait () "); 153 } 154 155 } 156 if (sem_destroy (tp_p->jobqueue->queueSem)!= 0){ 157 fprintf (stderr, "thpool_destory () : could not destroy semaphore "); 158 } 159 160 for (t = 0 ; t< (tp_p->threadsN) ; t++) 161 { 162 pthread_join (tp_p->threads[t], NULL); 163 } 164 thpool_jobqueue_empty (tp_p); 165 free (tp_p->threads); 166 free (tp_p->jobqueue->queueSem); 167 free (tp_p->jobqueue); 168 free (tp_p); 169 170 171 172 } 173 174 175 int thpool_jobqueue_init (thpool_t *tp_p) 176 { 177 tp_p->jobqueue = (thpool_jobqueue *)malloc (sizeof (thpool_jobqueue)); 178 if (tp_p->jobqueue == NULL) 179 { 180 fprintf (stderr ,"thpool_jobqueue malloc is error "); 181 return -1 ; 182 } 183 tp_p->jobqueue->tail = NULL ; 184 tp_p->jobqueue->head = NULL ; 185 tp_p->jobqueue->jobsN = 0 ; 186 return 0 ; 187 188 } 189 190 void thpool_jobqueue_add (thpool_t *tp_p , thpool_job_t *newjob_p){ 191 newjob_p->next = NULL ; 192 newjob_p->prev = NULL ; 193 194 thpool_job_t *oldfirstjob ; 195 oldfirstjob = tp_p->jobqueue->head; 196 197 198 switch (tp_p->jobqueue->jobsN) 199 { 200 case 0 : 201 tp_p->jobqueue->tail = newjob_p; 202 tp_p->jobqueue->head = newjob_p; 203 break; 204 default : 205 oldfirstjob->prev= newjob_p ; 206 newjob_p->next = oldfirstjob ; 207 tp_p->jobqueue->head= newjob_p; 208 break; 209 } 210 211 (tp_p->jobqueue->jobsN)++ ; 212 sem_post (tp_p->jobqueue->queueSem); //原子操作,信号量增加1 ,保证线程安全 213 214 int sval ; 215 sem_getvalue (tp_p->jobqueue->queueSem , &sval); //sval表示当前正在阻塞的线程数量 216 217 } 218 219 int thpool_jobqueue_removelast (thpool_t *tp_p){ 220 thpool_job_t *oldlastjob , *tmp; 221 oldlastjob = tp_p->jobqueue->tail ; 222 223 224 switch (tp_p->jobqueue->jobsN) 225 { 226 case 0 : 227 return -1 ; 228 break; 229 case 1 : 230 tp_p->jobqueue->head = NULL ; 231 tp_p->jobqueue->tail = NULL ; 232 break; 233 default : 234 tmp = oldlastjob->prev; 235 tmp->next = NULL ; 236 tp_p->jobqueue->tail = oldlastjob->prev; 237 238 } 239 (tp_p->jobqueue->jobsN) -- ; 240 int sval ; 241 sem_getvalue (tp_p->jobqueue->queueSem, &sval); 242 printf("sval:%d ", sval); 243 return 0 ; 244 } 245 thpool_job_t * thpool_jobqueue_peek (thpool_t *tp_p){ 246 return tp_p->jobqueue->tail ; 247 } 248 249 250 void thpool_jobqueue_empty (thpool_t *tp_p) 251 { 252 thpool_job_t *curjob; 253 curjob = tp_p->jobqueue->tail ; 254 while (tp_p->jobqueue->jobsN){ 255 tp_p->jobqueue->tail = curjob->prev ; 256 free (curjob); 257 curjob = tp_p->jobqueue->tail ; 258 tp_p->jobqueue->jobsN -- ; 259 } 260 tp_p->jobqueue->tail = NULL ; 261 tp_p->jobqueue->head = NULL ; 262 }
1 /*! 2 * Email: scictor@gmail.com 3 * Auth: scictor 4 * Date: 2019-9-9 5 * File: epoll_reactor_main.cpp 6 * Class: %{Cpp:License:ClassName} (if applicable) 7 * Brief: 8 * Note: 9 */ 10 #include <arpa/inet.h> 11 #include <unistd.h> 12 #include <assert.h> 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <sys/socket.h> 17 #include <sys/epoll.h> 18 #include <sys/types.h> 19 #include <pthread.h> 20 #include <fcntl.h> 21 #include <assert.h> 22 #include <errno.h> 23 #include <netinet/in.h> 24 #include "threadPool.h" 25 26 #define MAX_EVENT_NUMBER 1000 27 #define SIZE 1024 28 #define MAX 10 29 30 //从主线程向工作线程数据结构 31 struct fd 32 { 33 int epollfd; 34 int sockfd ; 35 }; 36 37 //用户说明 38 struct user 39 { 40 int sockfd ; //文件描述符 41 char client_buf [SIZE]; //数据的缓冲区 42 }; 43 struct user user_client[MAX]; //定义一个全局的客户数据表 44 45 /* 46 EPOLL事件有两种模型 Level Triggered (LT) 和 Edge Triggered (ET): 47 LT(level triggered,水平触发模式)是缺省的工作方式,并且同时支持 block 和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。 48 ET(edge-triggered,边缘触发模式)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次触发就绪事件。 49 */ 50 //由于epoll设置的EPOLLONESHOT模式,当出现errno =EAGAIN,就需要重新设置文件描述符(可读) 51 void reset_oneshot (int epollfd , int fd) 52 { 53 struct epoll_event event ; 54 event.data.fd = fd ; 55 /* 56 EPOLLONESHOT: 57 epoll有两种触发的方式即LT(水平触发)和ET(边缘触发)两种,在前者,只要存在着事件就会不断的触发,直到处理完成,而后者只触发一次相同事件或者说只在从非触发到触发两个状态转换的时候儿才触发。 58 这会出现下面一种情况,如果是多线程在处理,一个SOCKET事件到来,数据开始解析,这时候这个SOCKET又来了同样一个这样的事件,而你的数据解析尚未完成,那么程序会自动调度另外一个线程或者进程来处理新的事件,这造成一个很严重的问题,不同的线程或者进程在处理同一个SOCKET的事件,这会使程序的健壮性大降低而编程的复杂度大大增加!!即使在ET模式下也有可能出现这种情况!! 59 解决这种现象有两种方法,一种是在单独的线程或进程里解析数据,也就是说,接收数据的线程接收到数据后立刻将数据转移至另外的线程。 60 第二种方法就是本文要提到的EPOLLONESHOT这种方法,可以在epoll上注册这个事件,注册这个事件后,如果在处理写成当前的SOCKET后不再重新注册相关事件,那么这个事件就不再响应了或者说触发了。要想重新注册事件则需要调用epoll_ctl重置文件描述符上的事件,这样前面的socket就不会出现竞态这样就可以通过手动的方式来保证同一SOCKET只能被一个线程处理,不会跨越多个线程。 61 62 events 可以是以下几个宏的集合: 63 EPOLLIN //表示对应的文件描述符可以读(包括对端SOCKET正常关闭); 64 EPOLLOUT //表示对应的文件描述符可以写; 65 EPOLLPRI //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); 66 EPOLLERR //表示对应的文件描述符发生错误; 67 EPOLLHUP //表示对应的文件描述符被挂断; 68 EPOLLET //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。 69 EPOLLONESHOT//只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。 70 71 EPOLLIN事件: 72 当对方关闭连接(FIN), EPOLLERR,都可以认为是一种EPOLLIN事件,在read的时候分别有0,-1两个返回值。 73 */ 74 event.events = EPOLLIN|EPOLLET|EPOLLONESHOT ; 75 epoll_ctl (epollfd , EPOLL_CTL_MOD, fd , &event); 76 77 } 78 //向epoll内核事件表里面添加可写的事件 79 int addreadfd (int epollfd , int fd , int oneshot) 80 { 81 struct epoll_event event; 82 event.data.fd = fd ; 83 event.events |= ~ EPOLLIN ; 84 event.events |= EPOLLOUT ; 85 event.events |= EPOLLET; 86 if (oneshot) 87 { 88 event.events |= EPOLLONESHOT ; //设置EPOLLONESHOT 89 90 } 91 /* 92 EPOLL_CTL_ADD //注册新的fd到epfd中; 93 EPOLL_CTL_MOD //修改已经注册的fd的监听事件; 94 EPOLL_CTL_DEL //从epfd中删除一个fd; 95 */ 96 epoll_ctl (epollfd , EPOLL_CTL_MOD ,fd , &event); 97 98 } 99 //群聊函数,相当于放入缓存 100 int groupchat (int epollfd , int sockfd , char *buf) 101 { 102 int i = 0 ; 103 for ( i = 0 ; i < MAX ; i++) 104 { 105 if (user_client[i].sockfd == sockfd) 106 { 107 continue ; 108 } 109 strncpy (user_client[i].client_buf ,buf , strlen (buf)); 110 addreadfd (epollfd , user_client[i].sockfd , 1); 111 } 112 } 113 //接受数据的函数,也就是线程的回调函数 114 void *funcation (void *args) 115 { 116 int sockfd = ((struct fd*)args)->sockfd; 117 int epollfd =((struct fd*)args)->epollfd; 118 char buf[SIZE]; 119 memset (buf , '