epoll的EPOLLIN和EPOLLOU爲何不能同時關聯

轉自:http://blog.51cto.com/laokaddk/791945html

epoll的EPOLLIN和EPOLLOU爲何不能同時關聯linux

那麼在操做EPOLLIN時,發生的發送數據操做會不會響應?web

在操做EPOLLOUT時,客戶機發送的來的數據會不會丟失?apache

 

 

 

 

==================================================================================


如下轉自:http://blog.csdn.net/roen/archive/2007/03/21/1536148.aspx
(1)導言:

首 先,我強烈建議你們閱讀Richard Stevens著做《TCP/IP Illustracted Volume 1,2,3》和《UNIX Network Programming Volume 1,2》。雖然他離開咱們你們已經5年多了,可是他的書依然是進入網絡編程的最直接的道路。其中的3卷的《TCP/IP Illustracted》卷1是必讀-若是你不瞭解tcp協議各個選項的詳細定義,你就失去了優化程序重要的一個手段。卷2,3能夠選讀一下。好比卷2 講解的是4.4BSD內核TCP/IP協議棧實現----這個版本的協議棧幾乎影響了如今全部的主流os,可是由於年代久遠,內容不必定那麼vogue. 在這裏我多推薦一本《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》,以2.4內核講解Linux TCP/IP實現,至關不錯.做爲一個現實世界中的實現,不少時候你必須做不少權衡,這時候參考一個久經考驗的系統更有實際意義。舉個例子,linux內 核中sk_buff結構爲了追求速度和安全,犧牲了部份內存,因此在發送TCP包的時候,不管應用層數據多大,sk_buff最小也有272的字節.

其 實對於socket應用層程序來講,《UNIX Network Programming Volume 1》意義更大一點.2003年的時候,這本書出了最新的第3版本,不過主要仍是修訂第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevens給出了網絡IO的基本模型。在這裏最重要的莫過於select模型和Asynchronous I/O模型.從理論上說,AIO彷佛是最高效的,你的IO操做能夠當即返回,而後等待os告訴你IO操做完成。可是一直以來,如何實現就沒有一個完美的方 案。最著名的windows完成端口實現的AIO,實際上也是內部用線程池實現的罷了,最後的結果是IO有個線程池,你應用也須要一個線程池...... 不少文檔其實已經指出了這帶來的線程contexttch帶來的代價。

在linux 平臺上,關於網絡AIO一直是改動最多的地方,2.4的年代就有不少AIO內核patch,最著名的應該算是SGI那個。可是一直到2.6內核發佈,網絡 模塊的AIO一直沒有進入穩定內核版本(大部分都是使用用戶線程模擬方法,在使用了NPTL的linux上面其實和windows的完成端口基本上差很少 了)。2.6內核所支持的AIO特指磁盤的AIO---支持io_submit(),io_getevents()以及對Direct IO的支持(就是繞過VFS系統buffer直接寫硬盤,對於流服務器在內存平穩性上有至關幫助)。

因此,剩下的select模型基本上 就是咱們在linux上面的惟一選擇,其實,若是加上no-block socket的配置,能夠完成一個"僞"AIO的實現,只不過推進力在於你而不是os而已。不過傳統的select/poll函數有着一些沒法忍受的缺 點,因此改進一直是2.4-2.5開發版本內核的任務,包括/dev/poll,realtime signal等等。最終,Davide Libenzi開發的epoll進入2.6內核成爲正式的解決方案

(2)epoll的優勢

<1>支持一個進程打開大數目的socket描述符(FD)

select 最不能忍受的是一個進程所打開的FD是有必定限制的,由FD_SETSIZE設置,默認值是2048。對於那些須要支持的上萬鏈接數目的IM服務器來講顯 然太少了。這時候你一是能夠選擇修改這個宏而後從新編譯內核,不過資料也同時指出這樣會帶來網絡效率的降低,二是能夠選擇多進程的解決方案(傳統的 Apache方案),不過雖然linux上面建立進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,因此也不是一種完 美的方案。不過 epoll則沒有這個限制,它所支持的FD上限是最大能夠打開文件的數目,這個數字通常遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左 右,具體數目能夠 cat /proc/sys/fs/file-max 察看,通常來講這個數目和系統內存關係很大。

<2>IO效率不隨FD數目增長而線性降低

傳 統的select/poll另外一個致命弱點就是當你擁有一個很大的socket集合,不過因爲網絡延時,任一時間只有部分的socket是"活躍"的,但 是 select/poll每次調用都會線性掃描所有的集合,致使效率呈現線性降低可是epoll不存在這個問題,它只會對"活躍"的socket進行操 做 ---這是由於在內核實現中epoll是根據每一個fd上面的callback函數實現的。那麼,只有"活躍"的socket纔會主動的去調用 callback函數,其餘idle狀態socket則不會,在這點上,epoll實現了一個"僞"AIO,由於這時候推進力在os內核。在一些 benchmark中,若是全部的socket基本上都是活躍的---好比一個高速LAN環境,epoll並不比select/poll有什麼效率,相 反,若是過多使用epoll_ctl,效率相比還有稍微的降低。可是一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。

<3>使用mmap加速內核與用戶空間的消息傳遞。

這 點實際上涉及到epoll的具體實現了。不管是select,poll仍是epoll都須要內核把FD消息通知給用戶空間,如何避免沒必要要的內存拷貝就很 重要,在這點上,epoll是經過內核於用戶空間mmap同一塊內存實現的。而若是你想我同樣從2.5內核就關注epoll的話,必定不會忘記手工 mmap這一步的。

<4>內核微調

這一點其實不算epoll的優勢了,而是整個linux平臺的優勢。也許 你能夠懷疑linux平臺,可是你沒法迴避linux平臺賦予你微調內核的能力。好比,內核TCP/IP協議棧使用內存池管理sk_buff結構,那麼可 以在運行時期動態調整這個內存pool(skb_head_pool)的大小--- 經過echo XXXX>/proc/sys/net/core/hot_list_length完成。再好比listen函數的第2個參數(TCP完成3次握手 的數據包隊列長度),也能夠根據你平臺內存大小動態調整。更甚至在一個數據包面數目巨大但同時每一個數據包自己大小卻很小的特殊系統上嘗試最新的NAPI網 卡驅動架構。

(3)epoll的使用

使人高興的是,2.6內核的epoll比其2.5開發版本的/dev/epoll簡潔了許多,因此,大部分狀況下,強大的東西每每是簡單的。惟一有點麻煩是epoll有2種工做方式:LT和ET。

LT(level triggered)是缺省的工做方式,而且同時支持block和no-block socket.在這種作法中,內核告訴你一個文件描述符是否就緒了,而後你能夠對這個就緒的fd進行IO操做。若是你不做任何操做,內核仍是會繼續通知你 的,因此,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的表明.

ET (edge-triggered)是高速工做方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核經過epoll告訴你。而後它會假設你知道文件描述符已經就緒,而且不會再爲那個文件描述 符發送更多的就緒通知,直到你作了某些操做致使那個文件描述符再也不爲就緒狀態了(好比,你在發送,接收或者接收請求,或者發送接收的數據少於必定量時致使 了一個EWOULDBLOCK 錯誤)。可是請注意,若是一直不對這個fd做IO操做(從而致使它再次變成未就緒),內核不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍須要更多的benchmark確認。

epoll只有epoll_create,epoll_ctl,epoll_wait 3個系統調用,具體用法請參考http://www.xmailserver.org/linux-patches/nio-improve.html ,
在http://www.kegel.com/rn/也有一個完整的例子,你們一看就知道如何使用了

(4)Leader/follower模式線程pool實現,以及和epoll的配合

.....未完成,主要是要避免過多的epoll_ctl調用,以及嘗試使用EPOLLONESHOT加速......

(5)benchmark

.......未完成

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
一個簡單的基於epoll的web server,性能還不錯


我根據一個epoll的模型改了一個http server出來。只有129行,還能夠精簡很多,呵呵。
小測了一下,一秒鐘處理了一萬了請求。固然這裏只是把現成的東西輸出。沒考慮到發送數據處理。和請求的解析。
注意了,epoll只基於linux 2.6內核的。其餘平臺不能用。





/*-------------------------------------------------------------------------------------------------
gcc -o httpd httpd.c -lpthread
author: wyezl
2006.4.28
---------------------------------------------------------------------------------------------------*/

#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>

#define PORT 8888
#define MAXFDS 5000
#define EVENTSIZE 100

#define BUFFER "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nConnection: close\r\nContent-Type: text/html\r\n\r\nHello"

int epfd;
void *serv_epoll(void *p);
void setnonblocking(int fd)
{
int opts;
opts=fcntl(fd, F_GETFL);
if (opts < 0)
{
fprintf(stderr, "fcntl failed\n");
return;
}
opts = opts | O_NONBLOCK;
if(fcntl(fd, F_SETFL, opts) < 0)
{
fprintf(stderr, "fcntl failed\n");
return;
}
return;
}

int main(int argc, char *argv[])
{
int fd, cfd,opt=1;
struct epoll_event ev;
struct sockaddr_in sin, cin;
socklen_t sin_len = sizeof(struct sockaddr_in);
pthread_t tid;
pthread_attr_t attr;

epfd = epoll_create(MAXFDS);
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) <= 0)
{
fprintf(stderr, "socket failed\n");
return -1;
}
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));

memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_port = htons((short)(PORT));
sin.sin_addr.s_addr = INADDR_ANY;
if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0)
{
fprintf(stderr, "bind failed\n");
return -1;
}
if (listen(fd, 32) != 0)
{
fprintf(stderr, "listen failed\n");
return -1;
}

pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
if (pthread_create(&tid, &attr, serv_epoll, NULL) != 0)
{
fprintf(stderr, "pthread_create failed\n");
return -1;
}

while ((cfd = accept(fd, (struct sockaddr *)&cin, &sin_len)) > 0)
{
setnonblocking(cfd);
ev.data.fd = cfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
//printf("connect from %s\n",inet_ntoa(cin.sin_addr));
//printf("cfd=%d\n",cfd);
}

if (fd > 0)
close(fd);
return 0;
}

void *serv_epoll(void *p)
{
int i, ret, cfd, nfds;;
struct epoll_event ev,events[EVENTSIZE];
char buffer[512];

while (1)
{
nfds = epoll_wait(epfd, events, EVENTSIZE , -1);
//printf("nfds ........... %d\n",nfds);
for (i=0; i<nfds; i++)
{
if(events[i].events & EPOLLIN)
{
cfd = events[i].data.fd;
ret = recv(cfd, buffer, sizeof(buffer),0);
//printf("read ret..........= %d\n",ret);

ev.data.fd = cfd;
ev.events = EPOLLOUT | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, cfd, &ev);
}
else if(events[i].events & EPOLLOUT)
{
cfd = events[i].data.fd;
ret = send(cfd, BUFFER, strlen(BUFFER), 0);
//printf("send ret...........= %d\n", ret);

ev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, &ev);
//shutdown(cfd, 1);
close(cfd);

}
}
}
return NULL;
}




下面是測試結果:

[yangjian2@localhost bin]$ ./ab -c 50 -n 10000 [url]http://202.108.xxx.xxx:8888/[/url]
This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, [url]http://www.zeustech.net/[/url]
Copyright (c) 1998-2002 The Apache Software Foundation, [url]http://www.apache.org/[/url]

Benchmarking 202.108.xxx.xxx (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Finished 10000 requests


Server Software: 
Server Hostname: 202.108.xxx.xxx
Server Port: 8888

Document Path: /
Document Length: 5 bytes

Concurrency Level: 50
Time taken for tests: 0.921732 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Total transferred: 872088 bytes
HTML transferred: 50120 bytes
Requests per second: 10849.14 [#/sec] (mean)
Time per request: 4.609 [ms] (mean)
Time per request: 0.092 [ms] (mean, across all concurrent requests)
Transfer rate: 923.26 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 1 0.9 2 3
Processing: 1 2 0.5 2 4
Waiting: 0 1 0.3 1 3
Total: 4 4 0.2 4 5
WARNING: The median and mean for the initial connection time are not within a normal deviation
These results are probably not that reliable.

Percentage of the requests served within a certain time (ms)
50% 4
66% 4
75% 4
80% 4
90% 4
95% 4
98% 5
99% 5
100% 5 (longest request)

==================================================================================

關於上面這個例子的疑問:爲何對同一個 FD 要進行 EPOLLIN 和 EPOLLOUT 之間的切換,同時設置 EPOLLIN | EPOLLOUT 不能夠嗎?

找到的答案以下:

unexpected extra pollout events from epoll

On Sun, 26 Oct 2008, Paul P wrote:編程

The way epoll works, is by hooking into the existing kernel poll 
subsystem. It hooks into the poll wakeups, via callback, and it that way 
it knows that "something" is changed. Then it reads the status of a file 
via f_op->poll() to know the status.
What happens is that,  /* 答案1 */ if you listen for EPOLLIN|EPOLLOUT, when a packet 
arrives the callback hook is hit, and the file is put into a maybe-ready 
list. Maybe-ready because at the time of the callback,
  epoll has no clue 
of what happened.

After that,  via epoll_wait(), f_op->poll() is called to get the status 
of the file, and since POLLIN|POLLOUT is returned (and since you're 
listening for EPOLLIN|EPOLLOUT), that gets reported back to you.
  The 
POLLOUT event, by meaning a buffer-full->buffer-avail transition, 
did 
not really happen, but since POLLOUT is true, that gets reported back 
too.

Ok, so make sure I understand you correctly, you're saying that 
currently the kernel doesn't have awareness of the difference between 
EPOLLIN and EPOLLOUT events because  at the time of the event, both 
EPOLLIN/EPOLLOUT are returned from the kernel and that at least for the 
near term that's not going to change.
 At some point, we can expect the 
EPOLLOUT to give the correct event, but not till later than .28.


The kernel knows the difference between EPOLLIN and EPOLLOUT, of course. 
At the moment though, such condition is not reported during wakeups, and 
this is what is going to be changing.

經過上面的對話知道,緣由就是若是同時設置 EPOLLIN 和 EPOLLOUT ,當事件發生時,EPOLLIN 和 EPOLLOUT 都會被返回。windows

/* 答案2 */ The best way to do it ATM, is to wait for POLLOUT only when
really needed.

I'm a little unclear how to do this. If I set the epoll_wait call to 
wait for just epollin events, that's fine. But when I send a large 
buffer of data and use epoll_ctl to look for epollin|epollout events, 
don't I have the same problem? 


You do that by writing data until it's finished, or you get EAGAIN. If you 
get EAGAIN, you listen for EPOLLOUT.
Reading is same, but you'd wait for EPOLLIN.

上面這段話給出瞭如何使用 epoll ,那就是ATM (異步模式),讀和寫分開在不一樣的時間,交替進行。

本身寫了一個例子,驗證上面的說法,以下:

server端:

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>


#define SERVPORT 9527
#define MAXBUF 1024
#define MAXFDS 5000
#define EVENTSIZE 100


ssize_t readn(int fd, void *vptr, size_t n)
{
char *ptr;
size_t nleft;
ssize_t nread;

nleft = n;
ptr = vptr;
while(nleft > 0)
{
if( (nread = read(fd, ptr, nleft)) == -1)
{
if(errno == EINTR)
continue;
else if(errno == EAGAIN)
break;
else
return (-1);

}
else if(nread == 0)
break;

nleft -= nread;
ptr += nread;
}

return (n - nleft);
}

int setnonblocking(int fd)
{
int opts;
if( (opts = fcntl(fd, F_GETFL, 0)) == -1)
{
perror("fcntl");
return -1;
}

opts = opts | O_NONBLOCK;
if( (opts = fcntl(fd, F_SETFL, opts)) == -1)
{
perror("fcntl");
return -1;
}

return 0;
}

int main(void)
{
char buf[MAXBUF];
int len, n;

struct sockaddr_in servaddr;
int sockfd, listenfd, epollfd, nfds;

struct epoll_event ev;
struct epoll_event events[EVENTSIZE];

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERVPORT);

if( (epollfd = epoll_create(MAXFDS)) == -1)
{
perror("epoll");
exit(1);
}

if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}

if(setnonblocking(listenfd) == -1)
{
perror("setnonblocking");
exit(1);
}

if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
{
perror("bind");
exit(1);
}

if(listen(listenfd, 10) == -1)
{
perror("listen");
exit(1);
}


/* --------------------------------------------*/
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listenfd;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1)
{
perror("epoll_ctl");
exit(1);
}

for( ; ; )
{
if( (nfds = epoll_wait(epollfd, events, EVENTSIZE, -1)) == -1)
{
perror("epoll_wait");
exit(1);
}

for(n = 0; n < nfds; n++)
{
if(events[n].data.fd == listenfd)
{
while( (sockfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) > 0)
{
if(setnonblocking(sockfd) == -1)
{
perror("setnonblocking");
exit(1);
}
ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
ev.data.fd = sockfd;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1)
{
perror("epoll_ctl");
exit(1);
}
}
/*if(sockfd == -1)
{
perror("accept");
exit(1);
}*/
}
else
{
if(events[n].events & EPOLLIN)
{
if( (len = readn(events[n].data.fd, buf, MAXBUF)) == -1)
{
perror("readn");
exit(1);
}
else if(len == 0)
{
close(events[n].data.fd);
ev.data.fd = events[n].data.fd;
epoll_ctl(epollfd, EPOLL_CTL_DEL, events[n].data.fd, &ev);
}

buf[len] = '\0';
printf("received %s\n", buf);
}

if(events[n].events & EPOLLOUT)
{
printf("when EPOLLIN is triggered, EPOLLOUT is also triggered\n");
}
}
}
}
}

client 端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define SERVPORT 9527
#define MAXBUF 1024

int main(void)
{
char buf[MAXBUF];
struct sockaddr_in servaddr;
int fd;
int n, len;

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
servaddr.sin_port = htons(SERVPORT);

if( (fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}

if(connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
{
perror("connect");
exit(1);
}

for(n = 0; n < 10; n++)
{
snprintf(buf, MAXBUF, "time %d", n);
len = strlen(buf);
if(write(fd, buf, len) != len)
{
printf("write error");
close(fd);
exit(1);
}

}

close(fd);

return 0;
}

從驗證的結果知道:
1. 若是設置 EPOLLOUT ,新鏈接到來時,EPOLLOUT 被觸發。
2. 若是同時設置 EPOLLIN | EPOLLOUT ,EPOLLIN被觸發時,EPOLLOUT` 也被觸發。

==================================================================================安全

相關文章
相關標籤/搜索