epoll函數用法,還有點poll和select
1,LT的epoll是select和poll函數的改進版。
特點是,讀完緩沖區后,如果緩沖區還有內容的話,epoll_wait函數還會返回,直到把緩沖區全部讀完。
2,ET的epoll(阻塞)
特點是,讀完緩沖區后,不管緩沖區還有沒有內容,epoll_wait函數都不會再返回,直到對端再一次發送信息過來。估計有的讀者朋友會想到用while去讀,但是有個致命的問題,因為文件描述符是阻塞的,所以當全部讀完后,進程就會阻塞在recv函數那里,就不能夠再處理別的連接了。
3,ET的epoll(非阻塞),效率最高的使用方法。
特點是,讀完緩沖區后,不管緩沖區還有沒有內容,epoll_wait函數都不會再返回,直到對端再一次發送信息過來。但是可以事先用fcntl把文件描述符設置成非阻塞的方式,讓后用while一直去讀,當全部讀完后,recv函數也不會阻塞。
ET的epoll(非阻塞)的例子:
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char** argv){
int port = atoi(argv[1]);
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
listen(lfd, 5);
int efd = epoll_create(10);
struct epoll_event re;
re.events = EPOLLIN;
re.data.fd = lfd;
epoll_ctl(efd, EPOLL_CTL_ADD, lfd, &re);
struct epoll_event events[100];
while(1){
int ret = epoll_wait(efd, events, 100, -1);
printf("======================wait=======\n");
if(ret == -1){
perror("epoll_wait");
exit(1);
}
for(int i = 0; i < ret; ++i){
if(events[i].data.fd == lfd){
int cfd = accept(lfd, NULL, NULL);
int flags = fcntl(cfd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flags);
struct epoll_event re;
re.events = EPOLLIN | EPOLLET;
re.data.fd = cfd;
epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &re);
break;
}
char buf[3];
int ret;
while((ret = recv(events[i].data.fd, buf, sizeof buf, 0)) > 0){
write(STDOUT_FILENO, buf, ret);
}
if(ret == 0){
epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
printf("client disconnet\n");
}
else if(ret == -1 && errno == EAGAIN){
printf("read over\n");
}
}
}
}
poll函數例子:
#include <stdio.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv){
int port = atoi(argv[1]);
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
listen(lfd, 5);
struct pollfd pfd[1024];
for(int i = 0; i < 1024; ++i){
pfd[i].fd = -1;
}
pfd[0].fd = lfd;
pfd[0].events = POLLIN;
nfds_t maxfd = 0;
while(1){
int ret = poll(pfd, maxfd + 1, -1);
printf("--------------poll------\n");
if(pfd[0].revents & POLLIN){
int cfd = accept(lfd, NULL, NULL);
for(int i = 0; i < 1024; ++i){
if(pfd[i].fd == -1){
pfd[i].fd = cfd;
pfd[i].events = POLLIN;
maxfd++;
break;
}
}
continue;
}
for(int i = 0; i <= maxfd; ++i){
if(pfd[i].revents & POLLIN){
char buf[64];
int ret = recv(pfd[i].fd, buf, sizeof buf, 0);
if(ret == 0){
pfd[i].fd = -1;
close(pfd[i].fd);
printf("client is disconnet\n");
}
else{
write(STDOUT_FILENO, buf, ret);
}
}
}
}
}
通過對比epoll和poll的例子可以看出來:
- epoll不需要事先決定數組的大小。poll需要。
- epoll內部是用紅黑樹實現的效率,不會隨着連接的增多,而明顯的變低。poll是用鏈表實現的,所以性能隨着連接的增多而降低。poll還不能在windows下使用。epoll是跨平台的。
- 順便說下,select是用數組實現的,數組的大小由內核代碼寫死了,就是1024,所以想增大,只能重新編譯內核。但是select是在跨平台的。
關於EPOLLOUT的補足:內核檢查寫的緩沖區,如果寫緩沖區未滿,處於可寫的狀態,epoll_wait函數就會返回。否則阻塞。
- 水平模式:如果寫緩沖區未滿,epoll_wait會一直返回。
- 邊緣模式:epoll_wait會先返回一次;然后,寫緩沖區從滿的狀態變成了未滿的狀態,epoll_wait返回。
-注意點:調用send等函數的時候,如果寫緩沖區滿了的話,套接字如果是阻塞的,程序就費了,不再能相應任何事件。如果是非阻塞的話,send就會失敗,有些數據就丟失了。所以,正確的做法是,當監聽到EPOLLIN事件的時候,把數據讀出來后,不要直接調用send等函數,要:把當前節點從樹上刪掉,然后加入一個EPOLLOUT的節點上去,等待epoll_wait的下一次返回,epoll_wait返回了,說明肯定可寫。
select函數例子
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>
int main(){
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(12345);
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
listen(fd, 5);
fd_set readers, temp;
FD_ZERO(&readers);
FD_ZERO(&temp);
FD_SET(fd, &readers);
int maxfd = fd;
int selret = 0;
char rbuf[1024] = {0};
while(1){
temp = readers;
selret = select(maxfd + 1, &temp, NULL, NULL, NULL);
if(FD_ISSET(fd, &temp)){
//server
int cfd = accept(fd, NULL, 0);
maxfd = cfd;
FD_SET(cfd, &readers);
maxfd = maxfd < cfd ? cfd : maxfd;
continue;
}
//client
for(int i = fd + 1; i <= maxfd; ++i){
if(FD_ISSET(i, &temp)){
int ret = read(i, rbuf, sizeof(rbuf));
printf("recv:%s\n", rbuf);
if(ret == 0){
FD_CLR(i, &readers);
}
ret = write(i, rbuf, sizeof(rbuf));
}
}
}
}