linux 驚羣問題

1. 結論

對於驚羣的資料,網上特別多,良莠不齊,也不全面。看的時候,有的資料說,驚羣已經解決了,有的資料說,驚羣還沒解決。。 哪個纔是對的?!  一怒之下,在研究各種公開資料的基礎上,特意查對了linux源碼,總結了此文。希望對有需要的人略有幫助,希望各位大神輕拍,如有錯漏,不吝指教,感激不盡。([email protected]

先說結論吧:

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這個進程自己:


int s = socket(...)

bind(s, ...)

listen(s, ...)

fork()


Fork自己幾次之後,每個進程阻塞在accept()函數這裏

for(;;) {

    int client = accept(...);  //子進程阻塞在這了

    if (client < 0) continue;

    ...

}


在較老的unix系統中,當有連接到來時,accept()在每個阻塞在這的進程裏被喚醒。

但是,只有這些進程中的一個能夠真正的accept這個連接,其他的進程accept將返回EAGAIN

驚羣造成的結果是系統對用戶進程/線程頻繁的做無效的調度、上下文切換,系統系能大打折扣。

3. Linux內核解決驚羣的方案

linux 2.6版本之前,監聽同一個socket的進程會掛在一個等待隊列上,當請求到來時,會喚醒所有等待的子進程。

當時可以使用鎖解決這種驚羣問題。

代碼類似如下:

for(;;) {

    lock();// 互斥鎖

    int client = accept(...);

    unlock();

    if (client < 0) continue;

    ...

}


linux 2.6版本之後,通過引入一個標記位,解決掉了驚羣問題。

測試程序fork了兩個子進程,accept爲阻塞模式,監聽同一個socket

(測試程序源碼test.c 在本文檔第6章節中)

當客戶端connect這個socket時,顯然只應該有一個進程accept成功,哪個子進程會accept成功呢?

以下是測試平臺和結果:

系統版本:

$ uname -a

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中,我們來看代碼片斷: 

struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)// accept的原型函數

{

...

error = inet_csk_wait_for_connect(sk, timeo); // 等待連接

...

}

static int inet_csk_wait_for_connect(struct sock *sk, long timeo)

{

...

/*

 * True wake-one mechanism for incoming connections: only

 * one process gets woken up, not the 'whole herd'.

 * Since we do not 'race & poll' for established sockets

 * anymore, the common case will execute the loop only once.

 *

 * Subtle issue: "add_wait_queue_exclusive()" will be added

 * after any current non-exclusive waiters, and we know that

 * it will always _stay_ after any new non-exclusive waiters

 * because all non-exclusive waiters are added at the

 * beginning of the wait-queue. As such, it's ok to "drop"

 * our exclusiveness temporarily when we get woken up without

 * having to remove and re-insert us on the wait queue.

 */

for (;;) {

// 以上英文註釋已經說清楚了,只有一個進程會喚醒

// 非exclusive的元素會加在等待隊列前頭,exclusive的元素會加在所有非exclusive元素的後頭

prepare_to_wait_exclusive(sk_sleep(sk), &wait,

  TASK_INTERRUPTIBLE);

}

...

}

void

prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)

{

unsigned long flags;

///設置等待隊列的flag爲EXCLUSIVE,設置這個就是表示一次只會有一個進程被喚醒,我們等會就會看到這個標記的作用。

wait->flags |= WQ_FLAG_EXCLUSIVE; //注意這個標誌,喚醒的階段會使用這個標誌

spin_lock_irqsave(&q->lock, flags);

if (list_empty(&wait->task_list))

//  加入等待隊列

__add_wait_queue_tail(q, wait);

set_current_state(state);

spin_unlock_irqrestore(&q->lock, flags);

}


以上時accept的實現,我們繼續來看喚醒的部分

當有tcp連接完成,就會從半連接隊列拷貝sock到連接隊列,這個時候我們就可以喚醒阻塞的accept了。ok,我們來看關鍵的代碼,首先是tcp_v4_do_rcv: 

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)

{

...

if (sk->sk_state == TCP_LISTEN) {

struct sock *nsk = tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

if (nsk != sk) {

sock_rps_save_rxhash(nsk, skb);

if (tcp_child_process(sk, nsk, skb)) { // 關注這個函數

rsk = nsk;

goto reset;

}

return 0;

}

}

...

}

int tcp_child_process(struct sock *parent, struct sock *child,

      struct sk_buff *skb)

{

...

if (!sock_owned_by_user(child)) {

ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),

    skb->len);

/* Wakeup parent, send SIGIO 喚醒父進程*/

if (state == TCP_SYN_RECV && child->sk_state != state)

parent->sk_data_ready(parent, 0); // 通知父進程

}

... 

}


調用sk_data_ready通知父socket查閱資料我們知道tcp中這個函數是sock_def_readable。而這個函數會調用wake_up_interruptible_sync_poll來喚醒隊列

#define wake_up_interruptible_sync_poll(x, m) \

__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))

void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, void *key)

{

...

__wake_up_common(q, mode, nr_exclusive, wake_flags, key);

spin_unlock_irqrestore(&q->lock, flags);

...

}

// nr_exclusive是1

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, int wake_flags, void *key)

{

wait_queue_t *curr, *next;

list_for_each_entry_safe(curr, next, &q->task_list, task_list) {

unsigned flags = curr->flags;

if (curr->func(curr, mode, wake_flags, key) &&

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

break;

}

}


 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等事件機制後,

子進程進程處理連接事件程序更復雜,類似如下:

for(;;) {

    int interesting_fd = wait_for_fds();

    if (fd_need_accept(interesting_fd)) {

        int client = accept(interesting_fd, ...);

        if (client < 0) continue;

    }

    else if (fd_is_a_signal(interesting_fd)) {

        manage_uwsgi_signal(interesting_fd);

    }

    ...

}


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的邏輯

/*

 * Implement the event wait interface for the eventpoll file. It is the kernel

 * part of the user space epoll_wait(2).

 */

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,

int, maxevents, int, timeout)

{

...

/* Time to fish for events ... */

error = ep_poll(ep, events, maxevents, timeout);

...

}

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,

           int maxevents, long timeout)

{

....

        init_waitqueue_entry(&wait, current);

// 是不是很眼熟!!!! Exclusive !! 將exclusive的元素加入到等待隊列隊尾

        __add_wait_queue_exclusive(&ep->wq, &wait); // **NOTICE**

for (;;) {

//  如果事件隊列不爲空,就跳出循環,返回了

if (ep_events_available(ep) || timed_out)

break;

       ....

       // 如果事件隊列爲空,就睡覺了, 除非中途被喚醒

if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))

timed_out = 1;

}

....

}

 __add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)

{

// 設置標記爲WQ_FLAG_EXCLUSIVE,並加入隊尾

        wait->flags |= WQ_FLAG_EXCLUSIVE;

        __add_wait_queue(q, wait);

}


喚醒的程序在回調函數ep_poll_callback中,當設備就緒,ep_poll_callback就會被調用

static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

....

if (waitqueue_active(&ep->wq))

wake_up_locked(&ep->wq);

....

}

#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1)

void __wake_up_locked(wait_queue_head_t *q, unsigned int mode, int nr)

{

__wake_up_common(q, mode, nr, 0, NULL);

}

//  __wake_up_common函數還記得嗎??傳進來的nr_exclusive是1

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, int wake_flags, void *key)

{

wait_queue_t *curr, *next;

list_for_each_entry_safe(curr, next, &q->task_list, task_list) {

unsigned flags = curr->flags;

if (curr->func(curr, mode, wake_flags, key) &&

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

break;

}

}


 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的。 

類似於這樣

lock()

epoll_wait(...);

accept(...);

unlock(...); 


網上相關資料很多,源碼閱讀也並不困難,不再贅述


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

6. 測試源碼

Test.c

#include <sys/types.h>

#include <sys/socket.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <strings.h>

#define SERV_PORT  9999

int main(int argc,char **argv)

{

     int listenfd,connfd;

     pid_t  childpid,childpid2;

     socklen_t clilen;

     struct sockaddr_in cliaddr,servaddr;

    

     listenfd = socket(AF_INET,SOCK_STREAM,0);

     bzero(&servaddr,sizeof(servaddr));

     servaddr.sin_family = AF_INET;

     servaddr.sin_addr.s_addr = htonl (INADDR_ANY);

     servaddr.sin_port = htons (SERV_PORT);

     bind(listenfd,  (struct sockaddr *) &servaddr, sizeof(servaddr));

 listen(listenfd,1000);

     clilen = sizeof(cliaddr);

     if( (childpid = fork()) == 0)

     {

         while(1)

         {

             connfd = accept(listenfd,(struct sockaddr *) &cliaddr,&clilen);

             printf("fork 1 is [%d],error is %m\n",connfd);

         }

     }

     if( (childpid2 = fork()) == 0)

     {

         while(1){

             connfd = accept(listenfd,(struct sockaddr *) &cliaddr,&clilen);

             printf("fork 2 is [%d],error is %m\n",connfd);

         }

     }

     sleep(100);

     return 1;

}



Test1.c

#include <sys/socket.h>

#include <sys/types.h>

#include <sys/epoll.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <pthread.h>

#include <errno.h>

#define MAXLINE 100

#define OPEN_MAX 100

#define LISTENQ 20

#define SERV_PORT 8888

#define INFTIM 1000

//用於讀寫兩個的兩個方面傳遞參數

struct user_data

{

int fd;

unsigned int n_size;

char line[MAXLINE];

};

//聲明epoll_event結構體的變量,ev用於註冊事件,數組用於回傳要處理的事件

struct epoll_event ev, events[20];

int epfd;

pthread_mutex_t mutex;

pthread_cond_t cond1;

struct task *readhead = NULL, *readtail = NULL, *writehead = NULL;

int i, 

maxi, 

listenfd, 

connfd, 

sockfd, 

nfds;

unsigned int n;

struct user_data *data = NULL;

struct user_data *rdata = NULL;//用於讀寫兩個的兩個方面傳遞參數

socklen_t clilen;

struct sockaddr_in clientaddr;

struct sockaddr_in serveraddr;

void setnonblocking(int sock)

{

int opts;

opts = fcntl(sock, F_GETFL);

if (opts < 0)

{

perror("fcntl(sock,GETFL)");

exit(1);

}

opts = opts | O_NONBLOCK;

if (fcntl(sock, F_SETFL, opts) < 0)

{

perror("fcntl(sock,SETFL,opts)");

exit(1);

}

}

void init()

{

listenfd = socket(AF_INET, SOCK_STREAM, 0);//協議族、socket類型、協議(0表示選擇默認的協議)

//把socket設置爲非阻塞方式

setnonblocking(listenfd);

int reuse_socket = 1;

    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1){

        printf("setsockopt reuse-addr error!");

    }

bzero(&serveraddr, sizeof(serveraddr));

serveraddr.sin_family = AF_INET;

char local_addr[] = "0.0.0.0";

inet_aton(local_addr, &(serveraddr.sin_addr));//htons(SERV_PORT);

serveraddr.sin_port = htons(SERV_PORT);

bind(listenfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));

listen(listenfd, LISTENQ);

maxi = 0;

}

void work_cycle(int j)

{

//生成用於處理accept的epoll專用的文件描述符

epfd = epoll_create(256);

//設置與要處理的事件相關的文件描述符

ev.data.fd = listenfd;

//設置要處理的事件類型

ev.events = EPOLLIN | EPOLLET;

//註冊epoll事件

epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

for (;;)

{

//等待epoll事件的發生

nfds = epoll_wait(epfd, events, 20, 1000);

//處理所發生的所有事件

//printf("epoll_wait\n");

for (i = 0; i < nfds; ++i)

{

if (events[i].data.fd == listenfd)

{

connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clilen);

if (connfd < 0)

{

printf("process %d:connfd<0 accept failure\n",j);

continue;

}

setnonblocking(connfd);

char *str = inet_ntoa(clientaddr.sin_addr);

//std::cout << "connec_ from >>" << str << std::endl;

printf("process %d:connect_from >>%s listenfd=%d connfd=%d\n",j, str,listenfd, connfd);

//設置用於讀操作的文件描述符

ev.data.fd = connfd;

//設置用於注測的讀操作事件

ev.events = EPOLLIN | EPOLLET;

//註冊ev

epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

} else{

if (events[i].events & EPOLLIN)

{

printf("process %d:reading! connfd=%d\n",j,events[i].data.fd);

if ((sockfd = events[i].data.fd) < 0) continue;

data = (struct user_data *)malloc(sizeof(struct user_data));

if(data == NULL)

{

printf("process %d:user_data malloc error",j);

exit(1);

}

data->fd = sockfd;

if ((n = read(sockfd, data->line, MAXLINE)) < 0)

{

if (errno == ECONNRESET)

{

close(sockfd);

} else

printf("process %d:readline error\n",j);

if (data != NULL) {

free(data);

data = NULL;

}

}else {

if (n == 0)

{

close(sockfd);

printf("process %d:Client close connect!\n",j);

if (data != NULL) {

//delete data;

free(data);

data = NULL;

}

} else

{

data->n_size = n;

//設置需要傳遞出去的數據

ev.data.ptr = data;

//設置用於注測的寫操作事件

ev.events = EPOLLOUT | EPOLLET;

//修改sockfd上要處理的事件爲EPOLLOUT

epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

}

}

} else {

if (events[i].events & EPOLLOUT)

{

rdata = (struct user_data *) events[i].data.ptr;

sockfd = rdata->fd;

printf("process %d:writing! connfd=%d\n",j,sockfd);

write(sockfd, rdata->line, rdata->n_size);

//delete rdata;

free(rdata);

//設置用於讀操作的文件描述符

ev.data.fd = sockfd;

//設置用於注測的讀操作事件

ev.events = EPOLLIN | EPOLLET;

//修改sockfd上要處理的事件爲EPOLIN

epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

}

}

}

}

}

}

typedef void (*fun_ptr)(void);

void pxy_process()

{

printf("Hello,World!\n");

for(;;){

sleep(1);

//pxy_log_error(PXY_LOG_NOTICE,cycle->log,"sleep 1 second!\n");

}

exit(0);

}

int main()

{

int i;

int pid;

init();

for(i=0;i<3;i++)

{

pid = fork();

switch(pid){

case -1:

printf("fork sub process failed!\n");

break;

case 0:

work_cycle(i);

break;

default:

break;

}

}

while(1){

sleep(1);

}

}


Test2.c

#include <sys/socket.h>

#include <sys/types.h>

#include <sys/epoll.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <pthread.h>

#include <errno.h>

#define MAXLINE 100

#define OPEN_MAX 100

#define LISTENQ 20

#define SERV_PORT 8888

#define INFTIM 1000

//用於讀寫兩個的兩個方面傳遞參數

struct user_data

{

int fd;

unsigned int n_size;

char line[MAXLINE];

};

//聲明epoll_event結構體的變量,ev用於註冊事件,數組用於回傳要處理的事件

struct epoll_event ev, events[20];

int epfd;

pthread_mutex_t mutex;

pthread_cond_t cond1;

struct task *readhead = NULL, *readtail = NULL, *writehead = NULL;

int i, 

maxi, 

listenfd, 

connfd, 

sockfd, 

nfds;

unsigned int n;

struct user_data *data = NULL;

struct user_data *rdata = NULL;//用於讀寫兩個的兩個方面傳遞參數

socklen_t clilen;

struct sockaddr_in clientaddr;

struct sockaddr_in serveraddr;

void setnonblocking(int sock)

{

int opts;

opts = fcntl(sock, F_GETFL);

if (opts < 0)

{

perror("fcntl(sock,GETFL)");

exit(1);

}

opts = opts | O_NONBLOCK;

if (fcntl(sock, F_SETFL, opts) < 0)

{

perror("fcntl(sock,SETFL,opts)");

exit(1);

}

}

void init()

{

listenfd = socket(AF_INET, SOCK_STREAM, 0);//協議族、socket類型、協議(0表示選擇默認的協議)

//把socket設置爲非阻塞方式

setnonblocking(listenfd);

int reuse_socket = 1;

    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1){

        printf("setsockopt reuse-addr error!");

    }

bzero(&serveraddr, sizeof(serveraddr));

serveraddr.sin_family = AF_INET;

char local_addr[] = "0.0.0.0";

inet_aton(local_addr, &(serveraddr.sin_addr));//htons(SERV_PORT);

serveraddr.sin_port = htons(SERV_PORT);

bind(listenfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));

listen(listenfd, LISTENQ);

maxi = 0;

}

void work_cycle(int j)

{

//生成用於處理accept的epoll專用的文件描述符

//epfd = epoll_create(256);

//設置與要處理的事件相關的文件描述符

//ev.data.fd = listenfd;

//設置要處理的事件類型

ev.events = EPOLLIN | EPOLLET;

//註冊epoll事件

epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

for (;;)

{

//等待epoll事件的發生

nfds = epoll_wait(epfd, events, 20, 1000);

//處理所發生的所有事件

//printf("epoll_wait\n");

for (i = 0; i < nfds; ++i)

{

if (events[i].data.fd == listenfd)

{

connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clilen);

if (connfd < 0)

{

printf("process %d:connfd<0 accept failure\n",j);

continue;

}

setnonblocking(connfd);

char *str = inet_ntoa(clientaddr.sin_addr);

//std::cout << "connec_ from >>" << str << std::endl;

printf("process %d:connect_from >>%s listenfd=%d connfd=%d\n",j, str,listenfd, connfd);

//設置用於讀操作的文件描述符

ev.data.fd = connfd;

//設置用於注測的讀操作事件

ev.events = EPOLLIN | EPOLLET;

//註冊ev

epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

} else{

if (events[i].events & EPOLLIN)

{

printf("process %d:reading! connfd=%d\n",j,events[i].data.fd);

if ((sockfd = events[i].data.fd) < 0) continue;

data = (struct user_data *)malloc(sizeof(struct user_data));

if(data == NULL)

{

printf("process %d:user_data malloc error",j);

exit(1);

}

data->fd = sockfd;

if ((n = read(sockfd, data->line, MAXLINE)) < 0)

{

if (errno == ECONNRESET)

{

close(sockfd);

} else

printf("process %d:readline error\n",j);

if (data != NULL) {

free(data);

data = NULL;

}

}else {

if (n == 0)

{

close(sockfd);

printf("process %d:Client close connect!\n",j);

if (data != NULL) {

//delete data;

free(data);

data = NULL;

}

} else

{

data->n_size = n;

//設置需要傳遞出去的數據

ev.data.ptr = data;

//設置用於注測的寫操作事件

ev.events = EPOLLOUT | EPOLLET;

//修改sockfd上要處理的事件爲EPOLLOUT

epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

}

}

} else {

if (events[i].events & EPOLLOUT)

{

rdata = (struct user_data *) events[i].data.ptr;

sockfd = rdata->fd;

printf("process %d:writing! connfd=%d\n",j,sockfd);

write(sockfd, rdata->line, rdata->n_size);

//delete rdata;

free(rdata);

//設置用於讀操作的文件描述符

ev.data.fd = sockfd;

//設置用於注測的讀操作事件

ev.events = EPOLLIN | EPOLLET;

//修改sockfd上要處理的事件爲EPOLIN

epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

}

}

}

}

}

}

typedef void (*fun_ptr)(void);

void pxy_process()

{

printf("Hello,World!\n");

for(;;){

sleep(1);

//pxy_log_error(PXY_LOG_NOTICE,cycle->log,"sleep 1 second!\n");

}

exit(0);

}

int main()

{

int i;

int pid;

init();

//生成用於處理accept的epoll專用的文件描述符

epfd = epoll_create(256);

//設置與要處理的事件相關的文件描述符

ev.data.fd = listenfd;

for(i=0;i<3;i++)

{

pid = fork();

switch(pid){

case -1:

printf("fork sub process failed!\n");

break;

case 0:

work_cycle(i);

break;

default:

break;

}

}

while(1){

sleep(1);

}

}


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是怎麼解決它的 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章