1. 結論
對於驚羣的資料,網上特別多,良莠不齊,也不全面。看的時候,有的資料說,驚羣已經解決了,有的資料說,驚羣還沒解決。。 哪個纔是對的?! 一怒之下,在研究各種公開資料的基礎上,特意查對了linux源碼,總結了此文。希望對有需要的人略有幫助,希望各位大神輕拍,如有錯漏,不吝指教,感激不盡。([email protected])
先說結論吧:
1. Linux多進程accept系統調用的驚羣問題(注意,這裏沒有使用select、epoll等事件機制),在linux 2.6版本之前的版本存在,在之後的版本中解決掉了。
2. 使用select epoll等事件機制,在linux早期的版本中,驚羣問題依然存在(epoll_create在fork之前)。 原因與之前單純使用accept導致驚羣,原因類似。Epoll的驚羣問題,同樣在之後的某個版本部分解決了。
3. Epoll_create在fork之後調用,不能避免驚羣問題,Nginx使用互斥鎖,解決epoll驚羣問題。
備註:
1. 本文的幾個示例程序,跑在內核3.8.0-35-generic上, 複製的內核代碼epoll部分,來自內核版本3.6. 這兩個版本,epoll沒有重大變化,因此,試驗時,我認爲他們是一樣的。
2. Epoll的驚羣問題(epoll_create在fork之前),在某個版本被修復。我沒有去查證是在哪個版本被修復的,但是我試驗平臺內核3.8, 和我看的代碼版本3.6, 都是解決了。而在2.6.1版本是存在的
3. 驚羣問題出現在多進程 多線程之上。爲了簡便,所有的測試程序用的多進程模型。
2. 驚羣是什麼
在unix/linux歷史上有一個問題,驚羣(thundering herd)
驚羣是指多個進程/線程在等待同一資源時,每當資源可用,所有的進程/線程都來競爭資源的現象。
讓一個進程bind一個網絡地址(可能是AF_INET,AF_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_create在fork之前創建
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章節中)
動作: 連接 連接 連接 發送數據0 發送數據1 發送數據2
但是從輸出來看,建立連接2 建立連接0 建立連接1 處理數據2 處理數據0 處理數據1
因此,我們使用epoll_create()在fork之後創建,每個進程的讀寫事件,只註冊在自己進程的epoll中。
再次試驗(源碼文件test1.c在本文檔第6章節中)
如預期,處理數據階段,每個進程正確處理了自己的數據。Accept階段,出現了驚羣。
歡迎回到“驚羣”問題!!!
我們知道epoll對驚羣的修復,是建立在共享在同一個epoll結構上的。Epoll_create在fork之後執行,每個進程有單獨的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://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/ poll和epoll內核源代碼剖析二[轉]
http://blog.csdn.net/russell_tao/article/details/7204260 “驚羣”,看看nginx是怎麼解決它的