epoll使用例子

来源:互联网 发布:apache jmeter 3.1 编辑:程序博客网 时间:2024/04/29 05:31
epoll使用例子

名词解释:man epoll之后,得到如下结果:

NAME
       epoll - I/O event notification facility

SYNOPSIS
       #include <sys/epoll.h>

DESCRIPTION
       epoll is a variant of poll(2) that can be used either as Edge or Level
       Triggered interface and scales well to large numbers of  watched  fds.
       Three  system  calls  are provided to set up and control an epoll set:
       epoll_create(2), epoll_ctl(2), epoll_wait(2).

       An epoll set is connected to a file descriptor created  by  epoll_cre-
       ate(2).   Interest for certain file descriptors is then registered via
       epoll_ctl(2).  Finally, the actual wait is started by epoll_wait(2).

其实,一切的解释都是多余的,按照我目前的了解,EPOLL模型似乎只有一种格式,所以大家只要参考我下面的代码,就能够对EPOLL有所了解了,代码的解释都已经在注释中:

while (TRUE)
 {
  int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);//等待EPOLL时间的发生,相当于监听,至于相关的端口,需要在初始化EPOLL的时候绑定。
  if (nfds <= 0)
   continue;
  m_bOnTimeChecking = FALSE;
  G_CurTime = time(NULL);
  for (int i=0; i<nfds; i++)
  {
   try
   {
    if (m_events[i].data.fd == m_listen_http_fd)//如果新监测到一个HTTP用户连接到绑定的HTTP端口,建立新的连接。由于我们新采用了SOCKET连接,所以基本没用。
    {
     OnAcceptHttpEpoll ();
    }
    else if (m_events[i].data.fd == m_listen_sock_fd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
    {
     OnAcceptSockEpoll ();
    }
    else if (m_events[i].events & EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
    {
     OnReadEpoll (i);
    }

    OnWriteEpoll (i);//查看当前的活动连接是否有需要写出的数据。
   }
   catch (int)
   {
    PRINTF ("CATCH捕获错误\n");
    continue;
   }
  }
  m_bOnTimeChecking = TRUE;
  OnTimer ();//进行一些定时的操作,主要就是删除一些短线用户等。
 }

 其实EPOLL的精华,按照我目前的理解,也就是上述的几段短短的代码,看来时代真的不同了,以前如何接受大量用户连接的问题,现在却被如此轻松的搞定,真是让人不得不感叹。

今天搞了一天的epoll,想做一个高并发的代理程序。刚开始真是郁闷,一直搞不通,网上也有几篇介绍epoll的文章。但都不深入,没有将一些注意的地方讲明。以至于走了很多弯路,现将自己的一些理解共享给大家,以少走弯路。 

epoll用到的所有函数都是在头文件sys/epoll.h中声明,有什么地方不明白或函数忘记了可以去看一下。 
epoll和select相比,最大不同在于: 

1epoll返回时已经明确的知道哪个sokcet fd发生了事件,不用再一个个比对。这样就提高了效率。 
2select的FD_SETSIZE是有限止的,而epoll是没有限止的只与系统资源有关。
 

1、epoll_create函数
函数声明:int epoll_create(int size)
该 函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。随你定好了。只要你有空间。可参见上面与select之不同2. 

22、epoll_ctl函数 
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。 
参数: 
epfd:由 epoll_create 生成的epoll专用的文件描述符; 
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除 

fd:关联的文件描述符; 
event:指向epoll_event的指针; 
如果调用成功返回0,不成功返回-1 

用到的数据结构 
typedef union epoll_data { 
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t; 

struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};


如: 
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);


常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;


3、epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回发生事件数。

view plaincopy to clipboardprint?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <errno.h>  
  4. #include <string.h>  
  5. #include <sys/types.h>  
  6. #include <netinet/in.h>  
  7. #include <sys/socket.h>  
  8. #include <sys/wait.h>  
  9. #include <unistd.h>  
  10. #include <arpa/inet.h>  
  11. #include <openssl/ssl.h>  
  12. #include <openssl/err.h>  
  13. #include <fcntl.h>  
  14. #include <sys/epoll.h>  
  15. #include <sys/time.h>  
  16. #include <sys/resource.h>  
view plaincopy to clipboardprint?
  1. #define MAXBUF 1024  
  2. #define MAXEPOLLSIZE 10000  
view plaincopy to clipboardprint?
  1. /* 
  2. setnonblocking - 设置句柄为非阻塞方式 
  3. */  
  4. int setnonblocking(int sockfd)  
  5. {  
  6.     if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)  
  7.  {  
  8.         return -1;  
  9.     }  
  10.     return 0;  
  11. }  
view plaincopy to clipboardprint?
  1. /* 
  2. handle_message - 处理每个 socket 上的消息收发 
  3. */  
  4. int handle_message(int new_fd)  
  5. {  
  6.     char buf[MAXBUF + 1];  
  7.     int len;  
  8.       
  9.  /* 开始处理每个新连接上的数据收发 */  
  10.     bzero(buf, MAXBUF + 1);  
  11.      
  12.  /* 接收客户端的消息 */  
  13.     len = recv(new_fd, buf, MAXBUF, 0);  
  14.     if (len > 0)  
  15.  {  
  16.         printf  
  17.             ("%d接收消息成功:'%s',共%d个字节的数据\n",  
  18.              new_fd, buf, len);  
  19.  }  
  20.     else  
  21.  {  
  22.         if (len < 0)  
  23.      printf  
  24.                 ("消息接收失败!错误代码是%d,错误信息是'%s'\n",  
  25.                  errno, strerror(errno));  
  26.         close(new_fd);  
  27.         return -1;  
  28.     }  
  29.     /* 处理每个新连接上的数据收发结束 */  
  30.     return len;  
  31. }  
  32. /************关于本文档******************************************** 
  33. *filename: epoll-server.c 
  34. *purpose: 演示epoll处理海量socket连接的方法 
  35. *wrote by: zhoulifa(<a href="mailto:zhoulifa@163.com"& gt;zhoulifa@163.com</a>) 周立发(<a href="http: //zhoulifa.bokee.com">http://zhoulifa.bokee.com</a>) 
  36. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 
  37. *date time:2007-01-31 21:00 
  38. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 
  39. * 但请遵循GPL 
  40. *Thanks to:Google 
  41. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 
  42. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献! 
  43. *********************************************************************/  
  44. int main(int argc, char **argv)  
  45. {  
  46.     int listener, new_fd, kdpfd, nfds, n, ret, curfds;  
  47.     socklen_t len;  
  48.     struct sockaddr_in my_addr, their_addr;  
  49.     unsigned int myport, lisnum;  
  50.     struct epoll_event ev;  
  51.     struct epoll_event events[MAXEPOLLSIZE];  
  52.     struct rlimit rt;  
  53.  myport = 5000;  
  54.  lisnum = 2;   
  55.       
  56.  /* 设置每个进程允许打开的最大文件数 */  
  57.     rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;  
  58.     if (setrlimit(RLIMIT_NOFILE, &rt) == -1)   
  59.  {  
  60.         perror("setrlimit");  
  61.         exit(1);  
  62.     }  
  63.     else   
  64.     {  
  65.         printf("设置系统资源参数成功!\n");  
  66.     }  
view plaincopy to clipboardprint?
  1.     /* 开启 socket 监听 */  
  2.     if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)  
  3.     {  
  4.         perror("socket");  
  5.         exit(1);  
  6.     }  
  7.     else  
  8.     {  
  9.         printf("socket 创建成功!\n");  
  10.  }  
  11.    
  12.     setnonblocking(listener);  
view plaincopy to clipboardprint?
  1.     bzero(&my_addr, sizeof(my_addr));  
  2.     my_addr.sin_family = PF_INET;  
  3.     my_addr.sin_port = htons(myport);  
  4.     my_addr.sin_addr.s_addr = INADDR_ANY;  
view plaincopy to clipboardprint?
  1.     if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1)   
  2.     {  
  3.         perror("bind");  
  4.         exit(1);  
  5.     }   
  6.     else  
  7.     {  
  8.         printf("IP 地址和端口绑定成功\n");  
  9.     }  
  10.     if (listen(listener, lisnum) == -1)   
  11.     {  
  12.         perror("listen");  
  13.         exit(1);  
  14.     }  
  15.     else  
  16.     {  
  17.         printf("开启服务成功!\n");  
  18.      }  
  19.       
  20.  /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */  
  21.     kdpfd = epoll_create(MAXEPOLLSIZE);  
  22.     len = sizeof(struct sockaddr_in);  
  23.     ev.events = EPOLLIN | EPOLLET;  
  24.     ev.data.fd = listener;  
  25.     if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0)   
  26.    {  
  27.         fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);  
  28.         return -1;  
  29.     }  
  30.  else  
  31.     {  
  32.   printf("监听 socket 加入 epoll 成功!\n");  
  33.  }  
  34.     curfds = 1;  
  35.     while (1)   
  36.     {  
  37.         /* 等待有事件发生 */  
  38.         nfds = epoll_wait(kdpfd, events, curfds, -1);  
  39.         if (nfds == -1)  
  40.         {  
  41.             perror("epoll_wait");  
  42.             break;  
  43.         }  
  44.         /* 处理所有事件 */  
  45.         for (n = 0; n < nfds; ++n)  
  46.        {  
  47.             if (events[n].data.fd == listener)   
  48.             {  
  49.                    new_fd = accept(listener, (struct sockaddr *) &their_addr,&len);  
  50.                    if (new_fd < 0)   
  51.                   {  
  52.                        perror("accept");  
  53.                        continue;  
  54.                   }   
  55.                   else  
  56.               {  
  57.                         printf("有连接来自于: %d:%d, 分配的 socket 为:%d\n",  
  58.                              inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);  
  59.                   }  
  60.                   setnonblocking(new_fd);  
  61.                   ev.events = EPOLLIN | EPOLLET;  
  62.                   ev.data.fd = new_fd;  
  63.                   if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0)  
  64.                   {  
  65.                         fprintf(stderr, "把 socket '%d' 加入 epoll 失败!%s\n",  
  66.                             new_fd, strerror(errno));  
  67.                         return -1;  
  68.                   }  
  69.                   curfds++;  
  70.             }   
  71.             else  
  72.            {  
  73.                  ret = handle_message(events[n].data.fd);  
  74.                  if (ret < 1 && errno != 11)  
  75.                 {  
  76.                     epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);  
  77.                     curfds--;  
  78.                 }  
  79.             }  
  80.         }  
  81.     }  
  82.     close(listener);  
  83.     return 0;  
  84. }  

 epoll_wait 运行的原理是 等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。 并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。 


转自:



EPOLL使用注意

  (2007-05-25 09:52:25)
转载
标签:  

epoll

   

et

   

epoll_ctl

   

epoll_ctl_add

   

epoll_ctl_mod

  

网络通讯

   

tcp

分类: 开发经验

接上篇文章,这段时间一直对使用EPOLL关联socket的网络通讯进行测试,功能测试就不用说了,网络上有很多实现例子,都可以参考;我主要集中在压力测试和稳定情况的分析,现有了自己的一点体会,特贴出来供大家批驳。
首先描述一下测试环境:Linux服务器,内核版本:2.6.9,g++版本3.4.6,EPOLL都使用ET模式。
第一个体会是关于监听套接字m_hListenSocket和epoll关联。一般的例子都是创建监听套接字和EPOLL描述苻后使用如下代码建立关联:
    struct epoll_event struEvent;
    struEvent.events = EPOLLIN | EPOLLET;
    struEvent.data.fd = m_hListenSocket;
    epoll_ctl(m_hEpoll, EPOLL_CTL_ADD, m_hListenSocket, &struEvent);
建立连接后,任何客户端和服务器监听端口建立连接的功能测试都是正常的,可以建立连接、收发数据等。但开启压力测试后发现了新问题,150个连接没间隔1秒和服务器建立连接,发现EPOLL只返回给我130个请求,并建立了130个连接;反复测试了多次,都是如此,那剩余的客户端请求那里去了?接着测试了300个连接,发现EPOLL只返回给我280个请求,也吃掉了20个请求;如果先建立150个连接,再一个一个的请求会发生什么呢?测试的情况是第131个请求不是151个请求,是被EPOLL吃掉的请求出来了;也就是说新请求可以把EPOLL未投递给我的消息顶出来,EPOLL一直保持着20个请求不返回,为什么会有这种现象呢?解决办法是什么呢?
带着这些疑问,反复进行了测试,发现使用EPOLL提供的LT模式,没有这种问题,但CPU消耗实在是太高了,不能接受。
测试了接收和发送数据了TCP套接字,由于每次发送数据后都使用如下代码,和epoll重新建立了关联,所以没有发生过EPOLL吃掉消息的问题。注意如不使用代码再次建立关联,则不能使用户socket持续发送数据。
    struct epoll_event struEvent;
    struEvent.events = EPOLLIN | EPOLLOUT | EPOLLET;
    struEvent.data.fd = hSocket;
    epoll_ctl(m_hEpoll, EPOLL_CTL_MOD, hSocket, &struEvent);
同时获得这个启发,是不是ET模式下,接受客户端请求建立连接时,EPOLL也要和监听套接字m_hListenSocket重新建立关联呢 ,随即增加了如下代码进行了测试:
   epoll_ctl(m_hEpoll, EPOLL_CTL_MOD, m_hListenSocket, &struEvent);
这下好了,无论是150个连接还是300个,EPOLL都可以完整的反馈给我了。看来这一步是必须的,但为什么EPOLL一定要吃掉20个请求呢,这点是我最不能理解呢?如果没有关联,那第2个请求或第3个就应当不返回了,真诚希望能有高手给予一个明确的答复。


epoll 使用

epoll使用

epoll的工作原理是,你如果想进行IO操作时,先向epoll查询是否可读或可写,如果处于可读或可写状态后,epoll会通过epoll_wait函数通知你,此时你再进行进一步的recv或send操作。
epoll仅仅是一个异步事件的通知机制,其本身并不作任何的IO读写操作,它只负责告诉你是不是可以读或可以写了,而具体的读写操作,还要应用层自己来作。epoll仅提供这种机制也是非常好的,它保持了事件通知与IO操作之间彼此的独立性,使得epoll的使用更加灵活。
epoll用到的所有函数都是在头文件sys/epoll.h中声明的,下面简要说明所用到的数据结构和函数:

所用到的数据结构
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件,
epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据,例如一个client连接到服务器,服务器通过调用accept函数可以得到与这个client对应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段以便后面的读写操作在这个文件描述符上进行
a、*ptr: 通过指针ptr携带应用层数据, 当事件的通知到来时,它不仅告诉你发生了什么样的事件,还同时告诉这次事件所操作的数据是哪些
epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件可能的取值为:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
所用到的函数:
1、epoll_create函数
函数声明:int epoll_create(int size) 
该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围
2、epoll_ctl函数
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数: epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修
改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符;
event:指向epoll_event的指针;
如果调用成功返回0,不成功返回-1
3、epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;
返回发生事件数。
分类: 网络编程


第一次用epoll去实现一个服务器, 
之前并不清楚epoll的用法, 
了解之后才发现epoll服务器的主线程其实最好和处理业务的代码分开, 
也就是说: 
epoll响应外界的io请求,当epoll得到一个请求的时候,扔到一个消息队列中,然后epoll直接返回,再去等待io请求.而消息队列会通知多个线程去处理这些业务逻辑. 

epoll第一次用,消息队列更是第一次用,开始一直在想,怎么写个阻塞的队列,而且要有主动通知的功能,想了一会儿发现pthread_cond_wait和pthread_cond_signal可以实现,于是就简单的试试,下面的代码已经可以实现我刚才想要得到的那个模型,细节就不管了. 

对消息队列熟悉的同学请帮忙提点意见,并告诉我下还有哪些方法可以实现阻塞的消息队列. 

对代码的解释和描述都写到注释中了. 

C代码  收藏代码
  1. /* 
  2. 几个用到的类型定义以及全局变量bq 
  3. */  
  4. char smtp_cmd_format;  
  5. struct epoll_event ev, events[MAX_EPOLL_SIZE];  
  6. int kdpfd,nfds;  
  7. struct block_queue  
  8. {  
  9.     int queue[THREADS_COUNT];  
  10.     long size;  
  11.     pthread_cond_t cond;  
  12.     pthread_mutex_t mutex;  
  13. }block_queue_t;  
  14. block_queue_t bq;  
  15. struct block_queue_param  
  16. {  
  17.     void* func;  
  18.     void* queue;  
  19. }block_queue_param_t;  



C代码  收藏代码
  1. void *block_queue(void * param)  
  2. {  
  3.     void(* func)(void* );  
  4.     int fd,i;  
  5.     /* 
  6. 由于block_queue是pthread_create的回调方法, 
  7. 所以block_queue的参数必须是void*类型 
  8.        */  
  9.     block_queue_t* bque = (block_queue_param_t*)param->queue;  
  10.         /* 
  11. param->func是block_queue解锁时需要调用的函数, 
  12. 而这个函数的参数是一个int fd, 
  13. 该fd是消息队列中刚刚插入的一个元素. 
  14.        */  
  15.     func = (block_queue_param_t*)param->func;  
  16.       
  17.     for(;;)  
  18.     {  
  19. /* 
  20. lock->wait->unlock 
  21. 这是经典的模式, 
  22. 切记: 
  23. pthread_cond_wait的方法自带了先解锁,再等待,最后再加锁的代码 
  24. */  
  25.         pthread_mutex_lock(bque->mutex);  
  26. /* 
  27. 线程在pthread_cond_wait这里被block住了, 
  28. 当听到pthread_cond_signal通知的时候, 
  29. 内核会从阻塞队列里面通过先进先出的原则唤醒一个线程, 
  30. 这个线程会执行pthread_cond_wait之后的代码. 
  31. */  
  32.         pthread_cond_wait(bque->cond,bque->mutex);  
  33.           
  34.         if(bque->size==0)  
  35.         {  
  36. /* 
  37. 啥也不做 
  38. */  
  39.         }else  
  40.         {  
  41.             fd = bque->queue[0];  
  42. /* 
  43. 移动队列, 
  44. 由于该队列是简单的用数组保存fd, 
  45. 所以移动这个操作必不可少,但肯定性能比链表差很多, 
  46. 这是懒惰的代价 
  47. */  
  48.             for(i = 0; i < bque->size; ++i)  
  49.                 bque->queue[i] = bque->queue[i+1];  
  50.                 bque->queue[bque->size-1] = 0;  
  51.             bque->size--;  
  52. /* 
  53. 执行被唤醒后的方法,参数是刚刚插入到队列中的一个fd 
  54. */  
  55.                         func(fd);  
  56.         }  
  57.           
  58.         pthread_mutex_unlock(bque->mutex);  
  59.     }  
  60. }  
  61.   
  62. void insert_queue(struct block_queue *bque,int fd)  
  63. {  
  64. /* 
  65. 加锁->通知->解锁 
  66.  
  67. 将元素插入队列之前需要先加锁 
  68. */  
  69.     pthread_mutex_lock(bque->mutex);  
  70. /* 
  71. 检查队列目前的大小, 
  72. 检查1: 
  73. 当大小已经达到定义的数组大小THREADS_COUNT时, 
  74. 抛弃该fd,说明服务器忙不过来了,消息队列已经满了 
  75.  
  76. 检查2: 
  77. 当大小超过数组定义的大小THREADS_COUNT时, 
  78. 肯定发生了异常,那就直接退出服务吧. 
  79. */  
  80.     if(bque->size == THREADS_COUNT)  
  81.         return;  
  82. /* 
  83. bque->size其实也是队列末尾位置指针, 
  84. 当插入一个元素后,这个指针自然也要向后移动一位. 
  85. */  
  86.     bque->queue[bque->size+1] = fd;  
  87.     if(++bque->size > THREADS_COUNT)  
  88.     {  
  89.         fprintf(stderr,"Queue size over folow.%d",bque->size);  
  90.         exit 1;  
  91.     }  
  92. /* 
  93. 当元素插入bque队列时, 
  94. 该通过pthread_cond_signal通知内核去调度wait的线程了 
  95. */  
  96.     pthread_cond_signal(bque->cond);  
  97.     pthread_mutex_unlock(bque->mutex);  
  98.       
  99. }  
  100.   
  101. /* 
  102. init_threads代码是初始化线程组的, 
  103. 随便写写的,大家知道怎么实现就行 
  104. */  
  105. int init_threads()  
  106. {  
  107.     size_t i=0;  
  108.     block_queue_param_t bqp;  
  109. /* 
  110. smtp_echo是处理epoll扔进队列中的fd的函数, 
  111. 该方法实现了整个模型的业务逻辑, 
  112. 整体代码的IO处理+消息队列以及业务处理分的很清晰, 
  113. 三个模块每个只有一处代码和其它模块通讯,没有多少耦合. 
  114. */  
  115.     bqp.func = (void*)smtp_echo;  
  116.     bqp.queue = (void*)bq;  
  117.     pthread_cond_init(bqp.cond,NULL);  
  118.     pthread_mutex_init(bqp.mutex,NULL);  
  119.     for( i = 0; i < THREADS_COUNT; ++i)  
  120.     {  
  121.         pthread_t child_thread;  
  122.         pthread_attr_t child_thread_attr;  
  123.         pthread_attr_init(&child_thread_attr);  
  124.         pthread_attr_setdetachstate(&child_thread_attr,PTHREAD_CREATE_DETACHED);  
  125.         if( pthread_create(&child_thread,&child_thread_attr,block_queue, (void *)bqp) < 0 )  
  126.         {  
  127.             printf("pthread_create Failed : %s\n",strerror(errno));  
  128.             return 1;  
  129.         }  
  130.         else  
  131.         {  
  132.             printf("pthread_create Success : %d\n",(int)child_thread);  
  133.             return 0;  
  134.         }  
  135.     }  
  136.   
  137. }  
  138.   
  139. /* 
  140. handler是主线程访问的方法, 
  141. 主线程通过handler把一个fd扔到消息队列之后, 
  142. 不再做任何事情就直接返回了. 
  143.  
  144. 在我的应用中,主线程是个epoll实现的服务器, 
  145. 由于epoll被响应的时候会知道哪些fd已经就位, 
  146. 于是直接把就位的fd扔到消息队列中就好了, 
  147. 主线程在继续等待其它fd的响应,而不需要去关心fd如何被处理. 
  148. */  
  149.   
  150. int handler(void* fd)  
  151. {  
  152.     printf("handler:fd => %d\n",*(int *)(fd));  
  153.     insert_queue(&bq,fd);  
  154.     return 0;  
  155. }  
  156.   
  157.   
  158. /* 
  159. main函数是整个程序的入口点, 
  160. 也是epoll服务器的实现, 
  161. epoll的思想很精髓,用法很简单, 
  162. 只要把man 4 epoll_ctl的例子copy出来,就可用了, 
  163. 不过那个例子语法有点问题, 
  164. 而且events数组是用指针,应该用[]实现,因为指针没有分配空间. 
  165. */  
  166. int main(int argc, char **argv)  
  167. {     
  168.     int server_socket = init_smtp();  
  169.     int n;  
  170.       
  171.     if(init_threads() == 0)  
  172.         printf("Success full init_threads.");  
  173.       
  174.     smtp_cmd_format = "^([a-zA-Z0-9]) (.*)$";  
  175.     kdpfd = epoll_create(MAX_EPOLL_SIZE);  
  176.     ev.events = EPOLLIN | EPOLLET;  
  177.     ev.data.fd = server_socket;  
  178.     if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, server_socket, &ev) < 0) {  
  179.         fprintf(stderr, "epoll set insertion error: fd=%d < 0",  
  180.                 server_socket);  
  181.         return -1;  
  182.     }  
  183.       
  184. /* 
  185. epoll的使用看这里 
  186. */  
  187.     for(;;) {  
  188.         struct sockaddr_in local;  
  189.         socklen_t length = sizeof(local);  
  190.         int client;  
  191.           
  192.         nfds = epoll_wait(kdpfd, events, MAX_EPOLL_SIZE, -1);  
  193.   
  194. /* 
  195. 当没有事件要处理时,epoll会阻塞住, 
  196. 否则,内核会填充events数组,里面的每一个events[n].data.fd就是发生io时间的文件句柄 
  197. */  
  198.   
  199.         for(n = 0; n < nfds; ++n) {  
  200. /* 
  201. 这里要判断一下请求的来源, 
  202. if(events[n].data.fd == server_socket) { 
  203. 这里是新建的连接, 
  204. 因为io发生在server_socket上 
  205. } 
  206. else{ 
  207. 这里是已有的连接, 
  208. 因为fd!= server_socket 
  209. 那fd肯定是之前从server_socket接收到, 
  210. 并且通过epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) 
  211. 加入到kdpfd所指向的内存空间中. 
  212. kdpfd其实是个文件句柄,在epoll_create(MAX_EPOLL_SIZE)时得到 
  213. } 
  214. */  
  215.             if(events[n].data.fd == server_socket) {  
  216.                 client = accept(server_socket, (struct sockaddr *) &local,&length);  
  217.                 if(client < 0){  
  218.                     perror("accept");  
  219.                     continue;  
  220.                 }  
  221.                 setnonblocking(client);  
  222.                 smtp_cmd("220",client);  
  223.                 ev.events = EPOLLIN | EPOLLET;  
  224.                 ev.data.fd = client;  
  225.                 if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {  
  226.                     fprintf(stderr, "epoll set insertion error: fd=%d < 0",  
  227.                             client);  
  228.                     return -1;  
  229.                 }  
  230.             }  
  231.             else  
  232. /* 
  233. 当已有的fd发生io操作时, 
  234. 执行如下代码.也就是把fd扔到消息队列中. 
  235. */  
  236.                 if(handler((void *)&events[n].data.fd) != 0)  
  237.                     perror("handler ret != 0");  
  238.         }  
  239.     }  
  240.   
  241.     close(server_socket);  
  242.     return 0;  
  243. }  

0 0
原创粉丝点击