一起学习epoll

 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\n");
 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\n");
 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\n");
 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\n", 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\n");
 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\n");
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 ()\n");
153         }
154 
155     }
156     if (sem_destroy (tp_p->jobqueue->queueSem)!= 0){
157         fprintf (stderr, "thpool_destory () : could not destroy semaphore\n");
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\n");
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\n", 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 , '\0', SIZE);
120 
121     printf ("start new thread to receive data on fd :%d\n", sockfd);
122 
123     //由于我将epoll的工作模式设置为ET模式,所以就要用一个循环来读取数据,防止数据没有读完,而丢失。
124     while (1)
125     {
126         int ret = recv (sockfd ,buf , SIZE-1 , 0);
127         if (ret == 0)
128         {
129             close (sockfd);
130             break;
131         }
132         else if (ret < 0)
133         {
134             if (errno == EAGAIN)
135             {
136                 reset_oneshot (epollfd, sockfd);  //重新设置(上面已经解释了)
137                 break;
138             }
139         }
140         else
141         {
142             printf (" read data is %s\n", buf);
143 //            sleep (5);
144             groupchat (epollfd , sockfd, buf );
145         }
146 
147 
148     }
149     printf ("end thread receive  data on fd : %d\n", sockfd);
150     return NULL;
151 }
152 //这是重新注册,将文件描述符从可写变成可读
153 int addagainfd (int epollfd , int fd)
154 {
155     struct epoll_event event;
156     event.data.fd = fd;
157     event.events  |= ~EPOLLOUT ;
158     event.events = EPOLLIN|EPOLLET|EPOLLONESHOT;
159     epoll_ctl (epollfd , EPOLL_CTL_MOD , fd , &event);
160 }
161 //与前面的解释一样
162 int reset_read_oneshot (int epollfd , int sockfd)
163 {
164     struct epoll_event  event;
165     event.data.fd = sockfd ;
166     event.events = EPOLLOUT |EPOLLET |EPOLLONESHOT;
167     epoll_ctl (epollfd, EPOLL_CTL_MOD , sockfd , &event);
168     return 0 ;
169 
170 }
171 
172 //发送读的数据到所有客户端
173 //int readfun (void *args)
174 void *readfun (void *args)
175 {
176     int sockfd = ((struct fd *)args)->sockfd ;
177     int epollfd= ((struct fd*)args)->epollfd ;
178 
179     int ret = send (sockfd, user_client[sockfd].client_buf , strlen (user_client[sockfd].client_buf), 0); //发送数据
180     if (ret == 0 )
181     {
182         close (sockfd);
183         printf ("发送数据失败\n");
184         return (void *)-1 ;
185     }
186     else if (ret == EAGAIN)
187     {
188         reset_read_oneshot (epollfd , sockfd);
189         printf("send later\n");
190         return (void *)-1;
191     }
192     memset (&user_client[sockfd].client_buf , '\0', sizeof (user_client[sockfd].client_buf));
193     addagainfd (epollfd , sockfd);//重新设置文件描述符
194     return NULL;
195 }
196 //套接字设置为非阻塞
197 int setnoblocking (int fd)
198 {
199     int old_option = fcntl (fd, F_GETFL);
200     int new_option = old_option|O_NONBLOCK;
201     fcntl (fd , F_SETFL , new_option);
202     return old_option ;
203 }
204 
205 int addfd (int epollfd , int fd , int oneshot)
206 {
207     struct epoll_event  event;
208     event.data.fd = fd ;
209     event.events = EPOLLIN|EPOLLET ;
210     if (oneshot)
211     {
212         event.events |= EPOLLONESHOT ;
213 
214     }
215     epoll_ctl (epollfd , EPOLL_CTL_ADD ,fd ,  &event);
216     setnoblocking (fd);
217     return 0 ;
218 }
219 
220 
221 
222 int main(int argc, char *argv[])
223 {
224     struct sockaddr_in  address ;
225     const char *ip = "127.0.0.1";
226     int port  = 8086 ;
227 
228     memset (&address , 0 , sizeof (address));
229     address.sin_family = AF_INET ;
230     inet_pton (AF_INET ,ip , &address.sin_addr);
231     address.sin_port =htons( port) ;
232 
233 
234     int listenfd = socket (AF_INET, SOCK_STREAM, 0);
235     assert (listenfd >=0);
236     int reuse = 1;
237     setsockopt (listenfd , SOL_SOCKET , SO_REUSEADDR , &reuse , sizeof (reuse)); //端口重用,因为出现过端口无法绑定的错误
238     int ret = bind (listenfd, (struct sockaddr*)&address , sizeof (address));
239     assert (ret >=0 );
240 
241     ret = listen (listenfd , 5);
242     assert (ret >=0);
243 
244 
245     struct epoll_event events[MAX_EVENT_NUMBER];
246 
247     int epollfd = epoll_create (5); //创建内核事件描述符表
248     assert (epollfd != -1);
249     addfd (epollfd , listenfd, 0);
250 
251     thpool_t  *thpool ;  //线程池
252     thpool = thpool_init (5) ; //线程池的一个初始化
253 
254     while (1)
255     {
256         int ret = epoll_wait (epollfd, events, MAX_EVENT_NUMBER , -1);//等待就绪的文件描述符,这个函数会将就绪的复制到events的结构体数组中。
257         if (ret < 0)
258         {
259             printf ("poll failure\n");
260             break;
261         }
262         int i =0;
263         for ( i = 0; i < ret; i++ )
264         {
265             int sockfd = events[i].data.fd ;
266 
267             if (sockfd == listenfd)
268             {
269                 struct sockaddr_in client_address ;
270                 socklen_t  client_length = sizeof (client_address);
271                 int connfd = accept (listenfd , (struct sockaddr*)&client_address,&client_length);
272                 user_client[connfd].sockfd = connfd ;
273                 memset (&user_client[connfd].client_buf , '\0', sizeof (user_client[connfd].client_buf));
274                 addfd (epollfd , connfd , 1);//将新的套接字加入到内核事件表里面。
275             }
276             else if (events[i].events & EPOLLIN)
277             {
278                 struct fd    fds_for_new_worker ;
279                 fds_for_new_worker.epollfd = epollfd ;
280                 fds_for_new_worker.sockfd = sockfd ;
281 
282                 thpool_add_work (thpool, funcation ,&fds_for_new_worker);//将任务添加到工作队列中
283             }else if (events[i].events & EPOLLOUT)
284             {
285 
286                 struct  fd   fds_for_new_worker ;
287                 fds_for_new_worker.epollfd = epollfd ;
288                 fds_for_new_worker.sockfd = sockfd ;
289                 thpool_add_work (thpool, readfun , &fds_for_new_worker );//将任务添加到工作队列中
290             }
291 
292         }
293 
294     }
295 
296     thpool_destory (thpool);
297     close (listenfd);
298     return EXIT_SUCCESS;
299 }

 

这个例子只是作为学习参考,里面有一些问题,可以试着修改以此提高你对epoll的认识.

可以使用telnet作为测试客户端:$ telnet 127.0.0.1 8086

整理资料


posted @ 2019-09-09 06:57  PKICA  阅读(380)  评论(0编辑  收藏  举报