linux 惊群问题 http://blog.csdn.net/liujiyong7/article/details/43346829

来源:互联网 发布:cpu淘宝散片为什么便宜 编辑:程序博客网 时间:2024/05/16 07:51
 

linux 惊群问题

 1411人阅读 评论(1) 收藏 举报

目录(?)[+]

1. 结论

对于惊群的资料,网上特别多,良莠不齐,也不全面。看的时候,有的资料说,惊群已经解决了,有的资料说,惊群还没解决。。 哪个才是对的?!  一怒之下,在研究各种公开资料的基础上,特意查对了linux源码,总结了此文。希望对有需要的人略有帮助,希望各位大神轻拍,如有错漏,不吝指教,感激不尽。(814329735@qq.com)

先说结论吧:

1. Linux多进程accept系统调用的惊群问题(注意,这里没有使用selectepoll等事件机制),在linux 2.6版本之前的版本存在,在之后的版本中解决掉了。

2. 使用select epoll等事件机制,linux早期的版本中,惊群问题依然存在(epoll_createfork之前)。 原因与之前单纯使用accept导致惊群,原因类似。Epoll的惊群问题,同样在之后的某个版本部分解决了。

3. Epoll_createfork之后调用,不能避免惊群问题,Nginx使用互斥锁,解决epoll惊群问题。

备注:

1.  本文的几个示例程序,跑在内核3.8.0-35-generic上, 复制的内核代码epoll部分,来自内核版本3.6.   这两个版本,epoll没有重大变化,因此,试验时,我认为他们是一样的。

2. Epoll的惊群问题(epoll_createfork之前),在某个版本被修复。我没有去查证是在哪个版本被修复的,但是我试验平台内核3.8, 和我看的代码版本3.6, 都是解决了。而在2.6.1版本是存在的

3. 惊群问题出现在多进程 多线程之上。为了简便,所有的测试程序用的多进程模型。

2. 惊群是什么

unix/linux历史上有一个问题,惊群(thundering herd

惊群是指多个进程/线程在等待同一资源时,每当资源可用,所有的进程/线程都来竞争资源的现象。

让一个进程bind一个网络地址(可能是AF_INETAF_UNIX或者其他任何你想要的),然后fork这个进程自己:


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int s = socket(...)  
  2.   
  3. bind(s, ...)  
  4.   
  5. listen(s, ...)  
  6.   
  7. fork()  


Fork自己几次之后,每个进程阻塞在accept()函数这里

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for(;;) {  
  2.   
  3.     int client = accept(...);  //子进程阻塞在这了  
  4.   
  5.     if (client < 0) continue;  
  6.   
  7.     ...  
  8.   
  9. }  


在较老的unix系统中,当有连接到来时,accept()在每个阻塞在这的进程里被唤醒。

但是,只有这些进程中的一个能够真正的accept这个连接,其他的进程accept将返回EAGAIN

惊群造成的结果是系统对用户进程/线程频繁的做无效的调度、上下文切换,系统系能大打折扣。

3. Linux内核解决惊群的方案

linux 2.6版本之前,监听同一个socket的进程会挂在一个等待队列上,当请求到来时,会唤醒所有等待的子进程。

当时可以使用锁解决这种惊群问题。

代码类似如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for(;;) {  
  2.   
  3.     lock();// 互斥锁  
  4.   
  5.     int client = accept(...);  
  6.   
  7.     unlock();  
  8.   
  9.     if (client < 0) continue;  
  10.   
  11.     ...  
  12.   
  13. }  


linux 2.6版本之后,通过引入一个标记位,解决掉了惊群问题。

测试程序fork了两个子进程,accept为阻塞模式,监听同一个socket

(测试程序源码test.c 在本文档第6章节中)

当客户端connect这个socket时,显然只应该有一个进程accept成功,哪个子进程会accept成功呢?

以下是测试平台和结果:

系统版本:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. $ uname -a  
  2.   
  3. Linux liujiyong 3.8.0-35-generic #50-Ubuntu SMP Tue Dec 3 01:24:59 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux  


运行结果:


实验结果显示,在linux内核版本3.8中,每次只有一个进程唤醒,已经解决掉了惊群问题。

我们从源码中来看看进程是如何解决这个问题的?

首先我们知道当accept的时候,如果没有连接则会一直阻塞(没有设置非阻塞),而阻塞代码是在inet_csk_wait_for_connect中,我们来看代码片断: 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)// accept的原型函数  
  2.   
  3. {  
  4.   
  5. ...  
  6.   
  7. error = inet_csk_wait_for_connect(sk, timeo); // 等待连接  
  8.   
  9. ...  
  10.   
  11. }  
  12.   
  13. static int inet_csk_wait_for_connect(struct sock *sk, long timeo)  
  14.   
  15. {  
  16.   
  17. ...  
  18.   
  19. /* 
  20.  
  21.  * True wake-one mechanism for incoming connections: only 
  22.  
  23.  * one process gets woken up, not the 'whole herd'. 
  24.  
  25.  * Since we do not 'race & poll' for established sockets 
  26.  
  27.  * anymore, the common case will execute the loop only once. 
  28.  
  29.  * 
  30.  
  31.  * Subtle issue: "add_wait_queue_exclusive()" will be added 
  32.  
  33.  * after any current non-exclusive waiters, and we know that 
  34.  
  35.  * it will always _stay_ after any new non-exclusive waiters 
  36.  
  37.  * because all non-exclusive waiters are added at the 
  38.  
  39.  * beginning of the wait-queue. As such, it's ok to "drop" 
  40.  
  41.  * our exclusiveness temporarily when we get woken up without 
  42.  
  43.  * having to remove and re-insert us on the wait queue. 
  44.  
  45.  */  
  46.   
  47. for (;;) {  
  48.   
  49. // 以上英文注释已经说清楚了,只有一个进程会唤醒  
  50.   
  51. // 非exclusive的元素会加在等待队列前头,exclusive的元素会加在所有非exclusive元素的后头  
  52.   
  53. prepare_to_wait_exclusive(sk_sleep(sk), &wait,  
  54.   
  55.   TASK_INTERRUPTIBLE);  
  56.   
  57. }  
  58.   
  59. ...  
  60.   
  61. }  
  62.   
  63. void  
  64.   
  65. prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)  
  66.   
  67. {  
  68.   
  69. unsigned long flags;  
  70.   
  71. ///设置等待队列的flag为EXCLUSIVE,设置这个就是表示一次只会有一个进程被唤醒,我们等会就会看到这个标记的作用。  
  72.   
  73. wait->flags |= WQ_FLAG_EXCLUSIVE; //注意这个标志,唤醒的阶段会使用这个标志  
  74.   
  75. spin_lock_irqsave(&q->lock, flags);  
  76.   
  77. if (list_empty(&wait->task_list))  
  78.   
  79. //  加入等待队列  
  80.   
  81. __add_wait_queue_tail(q, wait);  
  82.   
  83. set_current_state(state);  
  84.   
  85. spin_unlock_irqrestore(&q->lock, flags);  
  86.   
  87. }  


以上时accept的实现,我们继续来看唤醒的部分

当有tcp连接完成,就会从半连接队列拷贝sock到连接队列,这个时候我们就可以唤醒阻塞的accept了。ok,我们来看关键的代码,首先是tcp_v4_do_rcv: 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)  
  2.   
  3. {  
  4.   
  5. ...  
  6.   
  7. if (sk->sk_state == TCP_LISTEN) {  
  8.   
  9. struct sock *nsk = tcp_v4_hnd_req(sk, skb);  
  10.   
  11. if (!nsk)  
  12.   
  13. goto discard;  
  14.   
  15. if (nsk != sk) {  
  16.   
  17. sock_rps_save_rxhash(nsk, skb);  
  18.   
  19. if (tcp_child_process(sk, nsk, skb)) { // 关注这个函数  
  20.   
  21. rsk = nsk;  
  22.   
  23. goto reset;  
  24.   
  25. }  
  26.   
  27. return 0;  
  28.   
  29. }  
  30.   
  31. }  
  32.   
  33. ...  
  34.   
  35. }  
  36.   
  37. int tcp_child_process(struct sock *parent, struct sock *child,  
  38.   
  39.       struct sk_buff *skb)  
  40.   
  41. {  
  42.   
  43. ...  
  44.   
  45. if (!sock_owned_by_user(child)) {  
  46.   
  47. ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),  
  48.   
  49.     skb->len);  
  50.   
  51. /* Wakeup parent, send SIGIO 唤醒父进程*/  
  52.   
  53. if (state == TCP_SYN_RECV && child->sk_state != state)  
  54.   
  55. parent->sk_data_ready(parent, 0); // 通知父进程  
  56.   
  57. }  
  58.   
  59. ...   
  60.   
  61. }  


调用sk_data_ready通知父socket查阅资料我们知道tcp中这个函数是sock_def_readable。而这个函数会调用wake_up_interruptible_sync_poll来唤醒队列

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #define wake_up_interruptible_sync_poll(x, m) \  
  2.   
  3. __wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))  
  4.   
  5. void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode,  
  6.   
  7. int nr_exclusive, void *key)  
  8.   
  9. {  
  10.   
  11. ...  
  12.   
  13. __wake_up_common(q, mode, nr_exclusive, wake_flags, key);  
  14.   
  15. spin_unlock_irqrestore(&q->lock, flags);  
  16.   
  17. ...  
  18.   
  19. }  
  20.   
  21. // nr_exclusive是1  
  22.   
  23. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
  24.   
  25. int nr_exclusive, int wake_flags, void *key)  
  26.   
  27. {  
  28.   
  29. wait_queue_t *curr, *next;  
  30.   
  31. list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
  32.   
  33. unsigned flags = curr->flags;  
  34.   
  35. if (curr->func(curr, mode, wake_flags, key) &&  
  36.   
  37. (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)  
  38.   
  39. break;  
  40.   
  41. }  
  42.   
  43. }  


 curr->func(curr, mode, wake_flags, key) 是注册函数的执行

(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

传进来的nr_exclusive是1, 所以flags & WQ_FLAG_EXCLUSIVE为真的时候,执行一次,就会跳出循环。  我们记得accept的时候,加到等待队列的元素就是WQ_FLAG_EXCLUSIVE的

4. Epoll为什么还有惊群

在使用epoll poll select kqueue等事件机制后,

子进程进程处理连接事件程序更复杂,类似如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for(;;) {  
  2.   
  3.     int interesting_fd = wait_for_fds();  
  4.   
  5.     if (fd_need_accept(interesting_fd)) {  
  6.   
  7.         int client = accept(interesting_fd, ...);  
  8.   
  9.         if (client < 0) continue;  
  10.   
  11.     }  
  12.   
  13.     else if (fd_is_a_signal(interesting_fd)) {  
  14.   
  15.         manage_uwsgi_signal(interesting_fd);  
  16.   
  17.     }  
  18.   
  19.     ...  
  20.   
  21. }  


wait_for_fds函数:它可能是select(), poll(),或者kqueue(),epoll()

我们以epoll为例

讨论epoll的惊群的时候,我们需要区分两种情况

Epoll_createfork之前创建

Epoll_create fork之后创建

下面分别讨论

Epoll_create()fork子进程之前

Epoll_create()fork子进程之前所有子进程共享epoll_create()创建的epfd

这种问题出现的惊群,与之前accept惊群的原因类似,当有事件发生时,等待同一个文件描述符的所有进程/线程,都将被唤醒。

为什么需要全部唤醒?有的资料是这么说的

因为内核不知道,你是否在等待文件描述符来调用accept()函数,还是做其他事情(信号处理,定时事件)

Epoll部分修复了惊群问题

与accept惊群的解决类似,epoll后来的版本(具体哪个版本,有待考证),修复了这个问题。

测试结果(源码文件test2.c在本文档第6章节中):


可以看到并没有惊群现象发生,每次只唤醒一个进程。

我们来看epoll是如何解决这个问题的,解决思路与accept的一致

下面是epoll_wait的逻辑

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  
  3.  * Implement the event wait interface for the eventpoll file. It is the kernel 
  4.  
  5.  * part of the user space epoll_wait(2). 
  6.  
  7.  */  
  8.   
  9. SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,  
  10.   
  11. int, maxevents, int, timeout)  
  12.   
  13. {  
  14.   
  15. ...  
  16.   
  17. /* Time to fish for events ... */  
  18.   
  19. error = ep_poll(ep, events, maxevents, timeout);  
  20.   
  21. ...  
  22.   
  23. }  
  24.   
  25. static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,  
  26.   
  27.            int maxevents, long timeout)  
  28.   
  29. {  
  30.   
  31. ....  
  32.   
  33.         init_waitqueue_entry(&wait, current);  
  34.   
  35. // 是不是很眼熟!!!! Exclusive !! 将exclusive的元素加入到等待队列队尾  
  36.   
  37.         __add_wait_queue_exclusive(&ep->wq, &wait); // **NOTICE**  
  38.   
  39. for (;;) {  
  40.   
  41. //  如果事件队列不为空,就跳出循环,返回了  
  42.   
  43. if (ep_events_available(ep) || timed_out)  
  44.   
  45. break;  
  46.   
  47.        ....  
  48.   
  49.        // 如果事件队列为空,就睡觉了, 除非中途被唤醒  
  50.   
  51. if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))  
  52.   
  53. timed_out = 1;  
  54.   
  55. }  
  56.   
  57. ....  
  58.   
  59. }  
  60.   
  61.  __add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)  
  62.   
  63. {  
  64.   
  65. // 设置标记为WQ_FLAG_EXCLUSIVE,并加入队尾  
  66.   
  67.         wait->flags |= WQ_FLAG_EXCLUSIVE;  
  68.   
  69.         __add_wait_queue(q, wait);  
  70.   
  71. }  


唤醒的程序在回调函数ep_poll_callback中,当设备就绪,ep_poll_callback就会被调用

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)  
  2.   
  3. {  
  4.   
  5. ....  
  6.   
  7. if (waitqueue_active(&ep->wq))  
  8.   
  9. wake_up_locked(&ep->wq);  
  10.   
  11. ....  
  12.   
  13. }  
  14.   
  15. #define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1)  
  16.   
  17. void __wake_up_locked(wait_queue_head_t *q, unsigned int mode, int nr)  
  18.   
  19. {  
  20.   
  21. __wake_up_common(q, mode, nr, 0, NULL);  
  22.   
  23. }  
  24.   
  25. //  __wake_up_common函数还记得吗??传进来的nr_exclusive是1  
  26.   
  27. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
  28.   
  29. int nr_exclusive, int wake_flags, void *key)  
  30.   
  31. {  
  32.   
  33. wait_queue_t *curr, *next;  
  34.   
  35. list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
  36.   
  37. unsigned flags = curr->flags;  
  38.   
  39. if (curr->func(curr, mode, wake_flags, key) &&  
  40.   
  41. (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)  
  42.   
  43. break;  
  44.   
  45. }  
  46.   
  47. }  


 curr->func(curr, mode, wake_flags, key) 是注册函数的执行

(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

传进来的nr_exclusive是1, 所以flags & WQ_FLAG_EXCLUSIVE为真的时候,执行一次,就会跳出循环。

Epoll_create()fork子进程之后

epoll_create()Fork之前还是之后,有神马区别呢?

Fork之前epoll_create的话,所有进程共享一个epoll红黑数。

如果我们只需要处理accept事件的话,貌似世界一片美好了。但是,epoll并不是只处理accept事件,accept后续的读写事件都需要处理,还有定时或者信号事件。

当连接到来时,我们需要选择一个进程来accept,这个时候,任何一个accept都是可以的。当连接建立以后,后续的读写事件,却与进程有了关联。一个请求与a进程建立连接后,后续的读写也应该由a进程来做。

当读写事件发生时,应该通知哪个进程呢?Epoll并不知道,因此,事件有可能错误通知另一个进程,这是不对的。实验中观察到了这种现象

(测试源码文件test2.c在本文档第6章节中)

动作:  连接  连接 连接 发送数据发送数据发送数据2

但是从输出来看,建立连接建立连接建立连接处理数据处理数据处理数据1

因此,我们使用epoll_create()fork之后创建,每个进程的读写事件,只注册在自己进程的epoll中。

再次试验(源码文件test1.c在本文档第6章节中)


如预期,处理数据阶段,每个进程正确处理了自己的数据。Accept阶段,出现了惊群。

欢迎回到惊群问题!!!

我们知道epoll对惊群的修复,是建立在共享在同一个epoll结构上的。Epoll_createfork之后执行,每个进程有单独的epoll 红黑树,等待队列,ready事件列表。因此,惊群再次出现了。

试验中,我们发现,有时候唤醒所有进程,有时候唤醒部分进程,为什么?

有部分资料说,因为事件已经被某些进程处理掉了,因此不用在通知另外还未通知到的进程了。并未看到代码的,有待确证。

5. Nginx使用epoll如何解决惊群

Nginx采用互斥锁

Nginx是在fork之后,再epoll_create的。 

类似于这样

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. lock()  
  2.   
  3. epoll_wait(...);  
  4.   
  5. accept(...);  
  6.   
  7. unlock(...);   


网上相关资料很多,源码阅读也并不困难,不再赘述


http://blog.csdn.net/russell_tao/article/details/7204260  

6. 测试源码

Test.c

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <sys/types.h>  
  2.   
  3. #include <sys/socket.h>  
  4.   
  5. #include <unistd.h>  
  6.   
  7. #include <arpa/inet.h>  
  8.   
  9. #include <stdio.h>  
  10.   
  11. #include <stdlib.h>  
  12.   
  13. #include <errno.h>  
  14.   
  15. #include <strings.h>  
  16.   
  17. #define SERV_PORT  9999  
  18.   
  19. int main(int argc,char **argv)  
  20.   
  21. {  
  22.   
  23.      int listenfd,connfd;  
  24.   
  25.      pid_t  childpid,childpid2;  
  26.   
  27.      socklen_t clilen;  
  28.   
  29.      struct sockaddr_in cliaddr,servaddr;  
  30.   
  31.       
  32.   
  33.      listenfd = socket(AF_INET,SOCK_STREAM,0);  
  34.   
  35.      bzero(&servaddr,sizeof(servaddr));  
  36.   
  37.      servaddr.sin_family = AF_INET;  
  38.   
  39.      servaddr.sin_addr.s_addr = htonl (INADDR_ANY);  
  40.   
  41.      servaddr.sin_port = htons (SERV_PORT);  
  42.   
  43.      bind(listenfd,  (struct sockaddr *) &servaddr, sizeof(servaddr));  
  44.   
  45.  listen(listenfd,1000);  
  46.   
  47.      clilen = sizeof(cliaddr);  
  48.   
  49.      if( (childpid = fork()) == 0)  
  50.   
  51.      {  
  52.   
  53.          while(1)  
  54.   
  55.          {  
  56.   
  57.              connfd = accept(listenfd,(struct sockaddr *) &cliaddr,&clilen);  
  58.   
  59.              printf("fork 1 is [%d],error is %m\n",connfd);  
  60.   
  61.          }  
  62.   
  63.      }  
  64.   
  65.      if( (childpid2 = fork()) == 0)  
  66.   
  67.      {  
  68.   
  69.          while(1){  
  70.   
  71.              connfd = accept(listenfd,(struct sockaddr *) &cliaddr,&clilen);  
  72.   
  73.              printf("fork 2 is [%d],error is %m\n",connfd);  
  74.   
  75.          }  
  76.   
  77.      }  
  78.   
  79.      sleep(100);  
  80.   
  81.      return 1;  
  82.   
  83. }  


Test1.c

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <sys/socket.h>  
  2.   
  3. #include <sys/types.h>  
  4.   
  5. #include <sys/epoll.h>  
  6.   
  7. #include <netinet/in.h>  
  8.   
  9. #include <arpa/inet.h>  
  10.   
  11. #include <fcntl.h>  
  12.   
  13. #include <unistd.h>  
  14.   
  15. #include <stdio.h>  
  16.   
  17. #include <stdlib.h>  
  18.   
  19. #include <string.h>  
  20.   
  21. #include <pthread.h>  
  22.   
  23. #include <errno.h>  
  24.   
  25. #define MAXLINE 100  
  26.   
  27. #define OPEN_MAX 100  
  28.   
  29. #define LISTENQ 20  
  30.   
  31. #define SERV_PORT 8888  
  32.   
  33. #define INFTIM 1000  
  34.   
  35. //用于读写两个的两个方面传递参数  
  36.   
  37. struct user_data  
  38.   
  39. {  
  40.   
  41. int fd;  
  42.   
  43. unsigned int n_size;  
  44.   
  45. char line[MAXLINE];  
  46.   
  47. };  
  48.   
  49. //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件  
  50.   
  51. struct epoll_event ev, events[20];  
  52.   
  53. int epfd;  
  54.   
  55. pthread_mutex_t mutex;  
  56.   
  57. pthread_cond_t cond1;  
  58.   
  59. struct task *readhead = NULL, *readtail = NULL, *writehead = NULL;  
  60.   
  61. int i,   
  62.   
  63. maxi,   
  64.   
  65. listenfd,   
  66.   
  67. connfd,   
  68.   
  69. sockfd,   
  70.   
  71. nfds;  
  72.   
  73. unsigned int n;  
  74.   
  75. struct user_data *data = NULL;  
  76.   
  77. struct user_data *rdata = NULL;//用于读写两个的两个方面传递参数  
  78.   
  79. socklen_t clilen;  
  80.   
  81. struct sockaddr_in clientaddr;  
  82.   
  83. struct sockaddr_in serveraddr;  
  84.   
  85. void setnonblocking(int sock)  
  86.   
  87. {  
  88.   
  89. int opts;  
  90.   
  91. opts = fcntl(sock, F_GETFL);  
  92.   
  93. if (opts < 0)  
  94.   
  95. {  
  96.   
  97. perror("fcntl(sock,GETFL)");  
  98.   
  99. exit(1);  
  100.   
  101. }  
  102.   
  103. opts = opts | O_NONBLOCK;  
  104.   
  105. if (fcntl(sock, F_SETFL, opts) < 0)  
  106.   
  107. {  
  108.   
  109. perror("fcntl(sock,SETFL,opts)");  
  110.   
  111. exit(1);  
  112.   
  113. }  
  114.   
  115. }  
  116.   
  117. void init()  
  118.   
  119. {  
  120.   
  121. listenfd = socket(AF_INET, SOCK_STREAM, 0);//协议族、socket类型、协议(0表示选择默认的协议)  
  122.   
  123. //把socket设置为非阻塞方式  
  124.   
  125. setnonblocking(listenfd);  
  126.   
  127. int reuse_socket = 1;  
  128.   
  129.     if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1){  
  130.   
  131.         printf("setsockopt reuse-addr error!");  
  132.   
  133.     }  
  134.   
  135. bzero(&serveraddr, sizeof(serveraddr));  
  136.   
  137. serveraddr.sin_family = AF_INET;  
  138.   
  139. char local_addr[] = "0.0.0.0";  
  140.   
  141. inet_aton(local_addr, &(serveraddr.sin_addr));//htons(SERV_PORT);  
  142.   
  143. serveraddr.sin_port = htons(SERV_PORT);  
  144.   
  145. bind(listenfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));  
  146.   
  147. listen(listenfd, LISTENQ);  
  148.   
  149. maxi = 0;  
  150.   
  151. }  
  152.   
  153. void work_cycle(int j)  
  154.   
  155. {  
  156.   
  157. //生成用于处理accept的epoll专用的文件描述符  
  158.   
  159. epfd = epoll_create(256);  
  160.   
  161. //设置与要处理的事件相关的文件描述符  
  162.   
  163. ev.data.fd = listenfd;  
  164.   
  165. //设置要处理的事件类型  
  166.   
  167. ev.events = EPOLLIN | EPOLLET;  
  168.   
  169. //注册epoll事件  
  170.   
  171. epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);  
  172.   
  173. for (;;)  
  174.   
  175. {  
  176.   
  177. //等待epoll事件的发生  
  178.   
  179. nfds = epoll_wait(epfd, events, 20, 1000);  
  180.   
  181. //处理所发生的所有事件  
  182.   
  183. //printf("epoll_wait\n");  
  184.   
  185. for (i = 0; i < nfds; ++i)  
  186.   
  187. {  
  188.   
  189. if (events[i].data.fd == listenfd)  
  190.   
  191. {  
  192.   
  193. connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clilen);  
  194.   
  195. if (connfd < 0)  
  196.   
  197. {  
  198.   
  199. printf("process %d:connfd<0 accept failure\n",j);  
  200.   
  201. continue;  
  202.   
  203. }  
  204.   
  205. setnonblocking(connfd);  
  206.   
  207. char *str = inet_ntoa(clientaddr.sin_addr);  
  208.   
  209. //std::cout << "connec_ from >><< str << std::endl;  
  210.   
  211. printf("process %d:connect_from >>%s listenfd=%d connfd=%d\n",j, str,listenfd, connfd);  
  212.   
  213. //设置用于读操作的文件描述符  
  214.   
  215. ev.data.fd = connfd;  
  216.   
  217. //设置用于注测的读操作事件  
  218.   
  219. ev.events = EPOLLIN | EPOLLET;  
  220.   
  221. //注册ev  
  222.   
  223. epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);  
  224.   
  225. } else{  
  226.   
  227. if (events[i].events & EPOLLIN)  
  228.   
  229. {  
  230.   
  231. printf("process %d:reading! connfd=%d\n",j,events[i].data.fd);  
  232.   
  233. if ((sockfd = events[i].data.fd) < 0) continue;  
  234.   
  235. data = (struct user_data *)malloc(sizeof(struct user_data));  
  236.   
  237. if(data == NULL)  
  238.   
  239. {  
  240.   
  241. printf("process %d:user_data malloc error",j);  
  242.   
  243. exit(1);  
  244.   
  245. }  
  246.   
  247. data->fd = sockfd;  
  248.   
  249. if ((n = read(sockfd, data->line, MAXLINE)) < 0)  
  250.   
  251. {  
  252.   
  253. if (errno == ECONNRESET)  
  254.   
  255. {  
  256.   
  257. close(sockfd);  
  258.   
  259. } else  
  260.   
  261. printf("process %d:readline error\n",j);  
  262.   
  263. if (data != NULL) {  
  264.   
  265. free(data);  
  266.   
  267. data = NULL;  
  268.   
  269. }  
  270.   
  271. }else {  
  272.   
  273. if (n == 0)  
  274.   
  275. {  
  276.   
  277. close(sockfd);  
  278.   
  279. printf("process %d:Client close connect!\n",j);  
  280.   
  281. if (data != NULL) {  
  282.   
  283. //delete data;  
  284.   
  285. free(data);  
  286.   
  287. data = NULL;  
  288.   
  289. }  
  290.   
  291. } else  
  292.   
  293. {  
  294.   
  295. data->n_size = n;  
  296.   
  297. //设置需要传递出去的数据  
  298.   
  299. ev.data.ptr = data;  
  300.   
  301. //设置用于注测的写操作事件  
  302.   
  303. ev.events = EPOLLOUT | EPOLLET;  
  304.   
  305. //修改sockfd上要处理的事件为EPOLLOUT  
  306.   
  307. epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);  
  308.   
  309. }  
  310.   
  311. }  
  312.   
  313. } else {  
  314.   
  315. if (events[i].events & EPOLLOUT)  
  316.   
  317. {  
  318.   
  319. rdata = (struct user_data *) events[i].data.ptr;  
  320.   
  321. sockfd = rdata->fd;  
  322.   
  323. printf("process %d:writing! connfd=%d\n",j,sockfd);  
  324.   
  325. write(sockfd, rdata->line, rdata->n_size);  
  326.   
  327. //delete rdata;  
  328.   
  329. free(rdata);  
  330.   
  331. //设置用于读操作的文件描述符  
  332.   
  333. ev.data.fd = sockfd;  
  334.   
  335. //设置用于注测的读操作事件  
  336.   
  337. ev.events = EPOLLIN | EPOLLET;  
  338.   
  339. //修改sockfd上要处理的事件为EPOLIN  
  340.   
  341. epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);  
  342.   
  343. }  
  344.   
  345. }  
  346.   
  347. }  
  348.   
  349. }  
  350.   
  351. }  
  352.   
  353. }  
  354.   
  355. typedef void (*fun_ptr)(void);  
  356.   
  357. void pxy_process()  
  358.   
  359. {  
  360.   
  361. printf("Hello,World!\n");  
  362.   
  363. for(;;){  
  364.   
  365. sleep(1);  
  366.   
  367. //pxy_log_error(PXY_LOG_NOTICE,cycle->log,"sleep 1 second!\n");  
  368.   
  369. }  
  370.   
  371. exit(0);  
  372.   
  373. }  
  374.   
  375. int main()  
  376.   
  377. {  
  378.   
  379. int i;  
  380.   
  381. int pid;  
  382.   
  383. init();  
  384.   
  385. for(i=0;i<3;i++)  
  386.   
  387. {  
  388.   
  389. pid = fork();  
  390.   
  391. switch(pid){  
  392.   
  393. case -1:  
  394.   
  395. printf("fork sub process failed!\n");  
  396.   
  397. break;  
  398.   
  399. case 0:  
  400.   
  401. work_cycle(i);  
  402.   
  403. break;  
  404.   
  405. default:  
  406.   
  407. break;  
  408.   
  409. }  
  410.   
  411. }  
  412.   
  413. while(1){  
  414.   
  415. sleep(1);  
  416.   
  417. }  
  418.   
  419. }  


Test2.c

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <sys/socket.h>  
  2.   
  3. #include <sys/types.h>  
  4.   
  5. #include <sys/epoll.h>  
  6.   
  7. #include <netinet/in.h>  
  8.   
  9. #include <arpa/inet.h>  
  10.   
  11. #include <fcntl.h>  
  12.   
  13. #include <unistd.h>  
  14.   
  15. #include <stdio.h>  
  16.   
  17. #include <stdlib.h>  
  18.   
  19. #include <string.h>  
  20.   
  21. #include <pthread.h>  
  22.   
  23. #include <errno.h>  
  24.   
  25. #define MAXLINE 100  
  26.   
  27. #define OPEN_MAX 100  
  28.   
  29. #define LISTENQ 20  
  30.   
  31. #define SERV_PORT 8888  
  32.   
  33. #define INFTIM 1000  
  34.   
  35. //用于读写两个的两个方面传递参数  
  36.   
  37. struct user_data  
  38.   
  39. {  
  40.   
  41. int fd;  
  42.   
  43. unsigned int n_size;  
  44.   
  45. char line[MAXLINE];  
  46.   
  47. };  
  48.   
  49. //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件  
  50.   
  51. struct epoll_event ev, events[20];  
  52.   
  53. int epfd;  
  54.   
  55. pthread_mutex_t mutex;  
  56.   
  57. pthread_cond_t cond1;  
  58.   
  59. struct task *readhead = NULL, *readtail = NULL, *writehead = NULL;  
  60.   
  61. int i,   
  62.   
  63. maxi,   
  64.   
  65. listenfd,   
  66.   
  67. connfd,   
  68.   
  69. sockfd,   
  70.   
  71. nfds;  
  72.   
  73. unsigned int n;  
  74.   
  75. struct user_data *data = NULL;  
  76.   
  77. struct user_data *rdata = NULL;//用于读写两个的两个方面传递参数  
  78.   
  79. socklen_t clilen;  
  80.   
  81. struct sockaddr_in clientaddr;  
  82.   
  83. struct sockaddr_in serveraddr;  
  84.   
  85. void setnonblocking(int sock)  
  86.   
  87. {  
  88.   
  89. int opts;  
  90.   
  91. opts = fcntl(sock, F_GETFL);  
  92.   
  93. if (opts < 0)  
  94.   
  95. {  
  96.   
  97. perror("fcntl(sock,GETFL)");  
  98.   
  99. exit(1);  
  100.   
  101. }  
  102.   
  103. opts = opts | O_NONBLOCK;  
  104.   
  105. if (fcntl(sock, F_SETFL, opts) < 0)  
  106.   
  107. {  
  108.   
  109. perror("fcntl(sock,SETFL,opts)");  
  110.   
  111. exit(1);  
  112.   
  113. }  
  114.   
  115. }  
  116.   
  117. void init()  
  118.   
  119. {  
  120.   
  121. listenfd = socket(AF_INET, SOCK_STREAM, 0);//协议族、socket类型、协议(0表示选择默认的协议)  
  122.   
  123. //把socket设置为非阻塞方式  
  124.   
  125. setnonblocking(listenfd);  
  126.   
  127. int reuse_socket = 1;  
  128.   
  129.     if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1){  
  130.   
  131.         printf("setsockopt reuse-addr error!");  
  132.   
  133.     }  
  134.   
  135. bzero(&serveraddr, sizeof(serveraddr));  
  136.   
  137. serveraddr.sin_family = AF_INET;  
  138.   
  139. char local_addr[] = "0.0.0.0";  
  140.   
  141. inet_aton(local_addr, &(serveraddr.sin_addr));//htons(SERV_PORT);  
  142.   
  143. serveraddr.sin_port = htons(SERV_PORT);  
  144.   
  145. bind(listenfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));  
  146.   
  147. listen(listenfd, LISTENQ);  
  148.   
  149. maxi = 0;  
  150.   
  151. }  
  152.   
  153. void work_cycle(int j)  
  154.   
  155. {  
  156.   
  157. //生成用于处理accept的epoll专用的文件描述符  
  158.   
  159. //epfd = epoll_create(256);  
  160.   
  161. //设置与要处理的事件相关的文件描述符  
  162.   
  163. //ev.data.fd = listenfd;  
  164.   
  165. //设置要处理的事件类型  
  166.   
  167. ev.events = EPOLLIN | EPOLLET;  
  168.   
  169. //注册epoll事件  
  170.   
  171. epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);  
  172.   
  173. for (;;)  
  174.   
  175. {  
  176.   
  177. //等待epoll事件的发生  
  178.   
  179. nfds = epoll_wait(epfd, events, 20, 1000);  
  180.   
  181. //处理所发生的所有事件  
  182.   
  183. //printf("epoll_wait\n");  
  184.   
  185. for (i = 0; i < nfds; ++i)  
  186.   
  187. {  
  188.   
  189. if (events[i].data.fd == listenfd)  
  190.   
  191. {  
  192.   
  193. connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clilen);  
  194.   
  195. if (connfd < 0)  
  196.   
  197. {  
  198.   
  199. printf("process %d:connfd<0 accept failure\n",j);  
  200.   
  201. continue;  
  202.   
  203. }  
  204.   
  205. setnonblocking(connfd);  
  206.   
  207. char *str = inet_ntoa(clientaddr.sin_addr);  
  208.   
  209. //std::cout << "connec_ from >><< str << std::endl;  
  210.   
  211. printf("process %d:connect_from >>%s listenfd=%d connfd=%d\n",j, str,listenfd, connfd);  
  212.   
  213. //设置用于读操作的文件描述符  
  214.   
  215. ev.data.fd = connfd;  
  216.   
  217. //设置用于注测的读操作事件  
  218.   
  219. ev.events = EPOLLIN | EPOLLET;  
  220.   
  221. //注册ev  
  222.   
  223. epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);  
  224.   
  225. } else{  
  226.   
  227. if (events[i].events & EPOLLIN)  
  228.   
  229. {  
  230.   
  231. printf("process %d:reading! connfd=%d\n",j,events[i].data.fd);  
  232.   
  233. if ((sockfd = events[i].data.fd) < 0) continue;  
  234.   
  235. data = (struct user_data *)malloc(sizeof(struct user_data));  
  236.   
  237. if(data == NULL)  
  238.   
  239. {  
  240.   
  241. printf("process %d:user_data malloc error",j);  
  242.   
  243. exit(1);  
  244.   
  245. }  
  246.   
  247. data->fd = sockfd;  
  248.   
  249. if ((n = read(sockfd, data->line, MAXLINE)) < 0)  
  250.   
  251. {  
  252.   
  253. if (errno == ECONNRESET)  
  254.   
  255. {  
  256.   
  257. close(sockfd);  
  258.   
  259. } else  
  260.   
  261. printf("process %d:readline error\n",j);  
  262.   
  263. if (data != NULL) {  
  264.   
  265. free(data);  
  266.   
  267. data = NULL;  
  268.   
  269. }  
  270.   
  271. }else {  
  272.   
  273. if (n == 0)  
  274.   
  275. {  
  276.   
  277. close(sockfd);  
  278.   
  279. printf("process %d:Client close connect!\n",j);  
  280.   
  281. if (data != NULL) {  
  282.   
  283. //delete data;  
  284.   
  285. free(data);  
  286.   
  287. data = NULL;  
  288.   
  289. }  
  290.   
  291. } else  
  292.   
  293. {  
  294.   
  295. data->n_size = n;  
  296.   
  297. //设置需要传递出去的数据  
  298.   
  299. ev.data.ptr = data;  
  300.   
  301. //设置用于注测的写操作事件  
  302.   
  303. ev.events = EPOLLOUT | EPOLLET;  
  304.   
  305. //修改sockfd上要处理的事件为EPOLLOUT  
  306.   
  307. epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);  
  308.   
  309. }  
  310.   
  311. }  
  312.   
  313. } else {  
  314.   
  315. if (events[i].events & EPOLLOUT)  
  316.   
  317. {  
  318.   
  319. rdata = (struct user_data *) events[i].data.ptr;  
  320.   
  321. sockfd = rdata->fd;  
  322.   
  323. printf("process %d:writing! connfd=%d\n",j,sockfd);  
  324.   
  325. write(sockfd, rdata->line, rdata->n_size);  
  326.   
  327. //delete rdata;  
  328.   
  329. free(rdata);  
  330.   
  331. //设置用于读操作的文件描述符  
  332.   
  333. ev.data.fd = sockfd;  
  334.   
  335. //设置用于注测的读操作事件  
  336.   
  337. ev.events = EPOLLIN | EPOLLET;  
  338.   
  339. //修改sockfd上要处理的事件为EPOLIN  
  340.   
  341. epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);  
  342.   
  343. }  
  344.   
  345. }  
  346.   
  347. }  
  348.   
  349. }  
  350.   
  351. }  
  352.   
  353. }  
  354.   
  355. typedef void (*fun_ptr)(void);  
  356.   
  357. void pxy_process()  
  358.   
  359. {  
  360.   
  361. printf("Hello,World!\n");  
  362.   
  363. for(;;){  
  364.   
  365. sleep(1);  
  366.   
  367. //pxy_log_error(PXY_LOG_NOTICE,cycle->log,"sleep 1 second!\n");  
  368.   
  369. }  
  370.   
  371. exit(0);  
  372.   
  373. }  
  374.   
  375. int main()  
  376.   
  377. {  
  378.   
  379. int i;  
  380.   
  381. int pid;  
  382.   
  383. init();  
  384.   
  385. //生成用于处理accept的epoll专用的文件描述符  
  386.   
  387. epfd = epoll_create(256);  
  388.   
  389. //设置与要处理的事件相关的文件描述符  
  390.   
  391. ev.data.fd = listenfd;  
  392.   
  393. for(i=0;i<3;i++)  
  394.   
  395. {  
  396.   
  397. pid = fork();  
  398.   
  399. switch(pid){  
  400.   
  401. case -1:  
  402.   
  403. printf("fork sub process failed!\n");  
  404.   
  405. break;  
  406.   
  407. case 0:  
  408.   
  409. work_cycle(i);  
  410.   
  411. break;  
  412.   
  413. default:  
  414.   
  415. break;  
  416.   
  417. }  
  418.   
  419. }  
  420.   
  421. while(1){  
  422.   
  423. sleep(1);  
  424.   
  425. }  
  426.   
  427. }  


7. 参考

http://blog.csdn.net/yankai0219/article/details/8453313 Nginx中的Epoll事件处理机制 

http://simohayha.iteye.com/blog/561424 linux已经不存在惊群现象

http://gmd20.wap.blog.163.com/w2/blogDetail.do;jsessionid=193DBC380EF5E8D6EB26BEC1E73945AA.blog84-8010?blogId=fks_087066085080085070093082084066072087087075080085095065087&showRest=true&p=6&hostID=gmd20 

http://blog.csdn.net/yanook/article/details/6582800  

linux2.6 kernel已经解决accept的惊群现象 

http://uwsgi-docs.readthedocs.org/en/latest/articles/SerializingAccept.html 

http://simohayha.iteye.com/blog/561424 

等待队列设置了标志位,唤醒一位,就停止

http://blog.csdn.net/hdutigerkin/article/details/7517390 epoll详细工作原理 

http://blog.163.com/hzr163_2004/blog/static/3308607520106194177905/  pollepoll内核源代码剖析二[]  

http://blog.csdn.net/russell_tao/article/details/7204260 “惊群”,看看nginx是怎么解决它的 


版权声明:本文为博主原创文章,未经博主允许不得转载。

0 0
原创粉丝点击