select:
1,select有數量限制。因爲一個進程所打開的FD(文件描述符)是有限制的,所以該函數調用可以操作的文件描述符有最大數量的限制,由FD_SETSIZE決定,內核默認的最大值是:32*32=1024
2,效率低。函數調用過後,需要輪詢所有的文件描述符來判斷哪個文件描述符可以讀寫。如果文件描述符的總量很大,而可以讀寫的文件描述符很少,那麼效率是很低的。
3. 內核/用戶空間 內存拷貝問題,如何讓內核把 FD 消息通知給用戶空間呢?在這個問題上 select 採取了內存拷貝方法。
poll是對select的改進,但是隻改進了第一個問題;後面兩個缺點一樣存在。
epoll:
1,數量不受限,可以認爲定義數量。
2,效率高。不採用輪詢方式,而只對可以讀寫的文件描述符進行操作。
epoll的使用方式如下:
epoll只有epoll_create,epoll_ctl,epoll_wait 3個系統調用。
1. int epoll_create(int size);
功能 :生成一個epoll專用的文件描述符(或句柄)。
參數 :size: 在該epoll fd上關注的最大socket fd數。 自從linux2.6.8之後,size參數是被忽略的。
返回值:生成的文件描述符。
需要注意:當創建好epoll句柄後,它就是會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件註冊函數,它不同於select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。
功能 :控制某個epoll文件描述符上的事件,可以註冊事件,修改事件,刪除事件。
參數 :
參數epfd :由epoll_create生成的epoll專用的文件描述符;
參數op :用三個宏來表示:
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
參數fd :關聯的文件描述符;
參數event :指向epoll_event的指針;
返回值: 0:成功;
-1:失敗;
struct epoll_event(第四個參數event)結構如下:
//保存觸發事件的某個文件描述符相關的數據(與具體使用方式有關)
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
// 感興趣的事件和被觸發的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,
這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,
需要再次把這個socket加入到EPOLL隊列裏
3. int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout);
功能 :輪詢I/O事件的發生。
參數 : epfd :由 epoll_create 生成的epoll專用的文件描述符,即epoll_create的返回值;
events :用於回傳待處理事件的數組;參數events 收集在epoll監控的事件中已經發生的事件。
參數events是分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組
中(events不可以是空指針,內核只負責把數據複製到這個events數組中,不會去幫助我們
在用戶態中分配內存)。
maxevents :每次能處理的事件數; 這個 maxevents的值不能大於創建epoll_create()時的size.
timeout :等待I/O事件發生的超時值;-1相當於阻塞,0相當於非阻塞;
返回值: >=0 :返回發生事件數;
-1 :錯誤;
如果函數調用成功,返回對應I/O上已準備好的文件描述符數目,如返回0表示已超時。
4. int epoll_pwait(與函數epoll_wait類似)
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
與epoll_wait的區別是可以通過最後一個參數設置阻塞過程中信號屏蔽字。
上面的函數原型等價於:
sigset_toriginmask;
sigpromask(SIG_SETMASK,&sigmask,& originmask);
ready = epoll_wait(epfd,&events,maxevents,timeout);
sigpromask(SIG_SETMASK,& originmask ,NULL);
示例如下:
服務器端代碼:
#include <sys/epoll.h>
#include <netinet/in.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
typedef struct sockaddr_in sockaddr_in ;
typedef struct sockaddr sockaddr ;
#define SER_PORT 8080
int nonblock(int fd){
int opt ;
opt = fcntl(fd,F_GETFL);
opt |= O_NONBLOCK ;
return fcntl(fd,F_SETFL,opt);
}
int main(int argc,char**argv){
sockaddr_in srv, cli ;
int listen_fd ,con_fd ;
socklen_t len;
int res ,nsize,ws;
char buf[255];
int epfd,ers;
struct epoll_event evn,events[50];
int i;
bzero(&srv,sizeof(srv));
bzero(&cli,sizeof(cli));
srv.sin_port= SER_PORT ;
srv.sin_family = AF_INET ;
listen_fd = socket(AF_INET,SOCK_STREAM,0);
int yes = 1;
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));
if(bind(listen_fd,(sockaddr*)&srv,sizeof(sockaddr))<0) {
perror("bind");
exit(0);
}
listen(listen_fd,100);
nonblock(listen_fd);
epfd = epoll_create(200);
evn.events = EPOLLIN|EPOLLET ;
evn.data.fd = listen_fd;
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&evn);
static int count ;
while(1){
ers = epoll_wait(epfd,events,100,5000);
if(ers<0 ){
perror("epoll_wait:");exit(0);
}else if(ers==0){
printf("time out:%d\n",count++);
continue ;
}
for(i=0;i<ers;i++){
if(events[i].data.fd == listen_fd){
con_fd = accept(listen_fd,(sockaddr*)&cli ,&len);
nonblock(con_fd);
printf("connect from:%s\n",inet_ntoa(cli.sin_addr));
evn.data.fd = con_fd;
evn.events = EPOLLIN | EPOLLET ;
epoll_ctl(epfd,EPOLL_CTL_ADD,con_fd,&evn);
}else if(events[i].events & EPOLLIN){
nsize = 0;
while((res=read(events[i].data.fd,buf+nsize,sizeof(buf)-1))>0){
nsize+= res;
}
if(res==0){
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
printf("a client over\n");
close(con_fd);
continue ;
}else if(res<0 && errno!=EAGAIN){
perror("read");
continue ;
}
buf[nsize]=0;
evn.data.fd = events[i].data.fd;
evn.events=EPOLLOUT|EPOLLET ;
epoll_ctl(epfd,EPOLL_CTL_MOD,events[i].data.fd,&evn);
}else if(events[i].events & EPOLLOUT){
nsize = strlen(buf);
ws = 0;
while(nsize>0){
ws=write(events[i].data.fd,buf,nsize);
nsize-=ws;
}
evn.data.fd = events[i].data.fd;
evn.events=EPOLLIN|EPOLLET ;
epoll_ctl(epfd,EPOLL_CTL_MOD,events[i].data.fd,&evn);
}else{
printf("others\n");
}
}
}
close(listen_fd);
return 0;
}
客戶端代碼:
#include <sys/epoll.h>
#include <netinet/in.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct sockaddr_in sockaddr_in ;
typedef struct sockaddr sockaddr ;
#define SER_PORT 8080
#define IP_ADDR "10.33.28.230"
int main(int argc,char**argv){
sockaddr_in srv, cli ;
int listen_fd ,con_fd ;
socklen_t len;
int res,ws ;
char buf[255];
bzero(&srv,sizeof(srv));
bzero(&cli,sizeof(cli));
srv.sin_port= SER_PORT ;
srv.sin_family = AF_INET ;
inet_pton(AF_INET,IP_ADDR,&srv.sin_addr);
listen_fd = socket(AF_INET,SOCK_STREAM,0);
if(connect(listen_fd,(sockaddr*)&srv,sizeof(sockaddr))<0){
perror("connect");
exit(0);
}
while(1){
res = read(STDIN_FILENO,buf,sizeof(buf)-1);
ws = write(listen_fd,buf,res);
res = read(listen_fd,buf,sizeof(buf)-1);
ws = write(STDOUT_FILENO,buf,res);
}
close(listen_fd);
return 0;
}