epoll多路复用
select效率随着描述符的增多,时间增多,因为内部是for循环。而epoll时间是固定的,不随着描述符的增多而增长。
在工作中不再使用select,而是用epoll.
epoll共有三个函数:
a int epoll_create(int size); //创建一个epoll句柄。现在epoll不限制数量,所以size现在失效了,写1就可以。
b int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //事件注册函数.dpfd是新创建的epoll句柄.op为EPOLL_CTL_ADD,表示 注册新的fd;为EPOLL_CTL_MOD,表示修改已经注册的fd的监听事件;为 EPOLL_CTL_DEL,表示从epfd中删除一个fd。fd是需要监听的fd。event 是告诉内核需要监听什么事。
typedef union epoll_data{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
struct epoll_event{
__uint32_t events; //events可以是几个宏的集合。常用的有:EPOLLIN,表示对应的文件描述符可以读(包括对端SOCKET正常
关闭)。EPOLLET,表示将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered) 来说的。
epoll_data_t data;
};
c int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); //等待事件的产生,类似于select()调用。epfd为要监听的句 柄.从内核得到事件的集合保存到events中。maxevents告之内核这个events有多大 (即,数组成员的个数).timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说 是永久阻塞).触发了几个,返回值就是几。
例:用epoll实现即时聊天:
//通过epoll来实现tcp即时通信:服务端 #define NUM 10 int main(int argc,char* argv[]){ if(argc!=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2])); ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); if(-1==ret){ perror("bind"); return -1; } ret=listen(sfd,NUM); if(-1==ret){ perror("listen"); return -1; } int epfd=epoll_create(1); //创建epoll句柄 struct epoll_event event,evs[NUM+1]; //event保存注册的事件的信息,evs 用来保存epoll_wait监听到了的事件 event.events=EPOLLIN; event.data.fd=sfd; ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //在epfd中注册sfd if(-1==ret){ perror("epoll_ctl"); return -1; } event.events=EPOLLIN; event.data.fd=0; ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event); //注册标准输入 if(-1==ret){ perror("epoll_ctl"); return -1; } int i; int new_fd; char buf[128]; int n; while(1){ memset(evs,0,sizeof(evs)); //每次都要清空,以便wait重新填写 ret=epoll_wait(epfd,evs,NUM+1,-1);//监听epfd,监听的结果放到evs数组 中,数组的元素个数为NUM+1,-1表示一直监听. if(ret >0){ for(i=0;i<ret;i++){ //返回到了数组中,所以循环从evs重查看 if(evs[i].events == EPOLLIN &&evs[i].data.fd == sfd){//新请求 new_fd=accept(sfd,NULL,NULL); printf("new_fd=%d\n",new_fd); event.events=EPOLLIN; event.data.fd=new_fd; epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event);//注册 } if(evs[i].events == EPOLLIN &&evs[i].data.fd == 0){ //输入 memset(buf,0,sizeof(buf)); n=read(0,buf,sizeof(buf)); if(n>0){ send(new_fd,buf,strlen(buf)-1,0); }else if(n==0){ //在新的一行输入ctrl+d,表示EOF printf("bye\n"); event.events=EPOLLIN; event.data.fd=new_fd; epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event); close(new_fd); } } if(evs[i].events == EPOLLIN &&evs[i].data.fd == new_fd){//对 方向你发送消息 memset(buf,0,sizeof(buf)); n=recv(new_fd,buf,sizeof(buf),0); if(n>0){ printf("%s\n",buf); }else if(n==0){ //对方关闭连接了 printf("bye\n"); event.events=EPOLLIN; event.data.fd=new_fd; epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event); close(new_fd); break; } } } } } return 0; } | //客户端 int main(int argc,char** argv){ if(argc !=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2])); ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); if(-1==ret){ perror("connect"); return -1; } int epfd=epoll_create(1); //创建epoll句柄 struct epoll_event event,evs[2]; //只监听2个 event.events=EPOLLIN; event.data.fd=sfd; ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //在epfd中注册sfd if(-1==ret){ perror("epoll_ctl"); return -1; } event.events=EPOLLIN; event.data.fd=0; ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event); //注册标准输入 if(-1==ret){ perror("epoll_ctl"); return -1; } int i; char buf[128]; int n; while(1){ memset(evs,0,sizeof(evs)); ret=epoll_wait(epfd,evs,2,-1); if(ret>0){ for(i=0;i<ret;i++){ if(evs[i].events == EPOLLIN && evs[i].data.fd == 0){ memset(buf,0,sizeof(buf)); n=read(0,buf,sizeof(buf)); if(n==0){ //ctrl+d 结束 printf("bye\n"); close(sfd); return 0; } n=send(sfd,buf,strlen(buf)-1,0); if(-1==n){ perror("send"); return -1; } } if(evs[i].events == EPOLLIN && evs[i].data.fd == sfd){ memset(buf,0,sizeof(buf)); n=recv(sfd,buf,sizeof(buf),0); if(n > 0){ printf("%s\n",buf); }else if(n==0){ printf("bye\n"); close(sfd); return 0; } } } } } return 0; } |
注:在上面的程序中,buf一次能够读128个字符,但是如果对方一次给我传输256个字符,我第一次只能接收128个,然后输出。等到下一次while循环时,epoll_wait会继续触发,我再接收剩下的128个字符。这样不仅我每次只能接收一部分,要分成很多段来显示。而且给epoll_wait增加了压力。
我们希望,1.针对客户端的每一次发送的数据,epoll_wait只触发一次。2.我们想一次把缓冲区里面的数据一次读完。
EPOLL 事件有两种模型:
Edge Triggered (ET) 边缘触发:只有数据到来,才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发:只要有数据都会触发。
水平触发:当缓冲区有数据时,epoll_wait会不断得到触发。
如果发的长度大于buf的长度,会分成多段来打。。。
新的场景:
1.针对客户端的每一次发送的数据,epoll_wait只触发一次。
2.我们想一次把缓冲区里面的数据一次读完。
如果数据来了,我buf比较小,需要读多次。面临两个问题,1.
边缘触发:只有在上升沿的时候才触发。
改成之后:若客户端发送I come from china .则只触发一次。比如buf为10,则只发送I come fro
发送几次数据,我就响应几次。
例:将描述符的属性改为非阻塞
void change_noblock(int fd){
int status;
status=fcntl(fd,F_GETFL); //fcntl函数可以改变已打开的文件描述符性质。F_GETFL表示读取文件描述符状态。
status=status|O_NONBLOCK; //将状态改为非阻塞。要想再变回来,与上按位取反。
int ret=fcntl(fd,F_SETFL,status); //设置文件描述符状态,status为新状态。
if(-1==ret){
perror("fcntl");
return;
}
}
int main(){
char buf[128]={0};
change_noblock(0); //调用函数,将标准输入改为非阻塞,0为标准输入
int ret=read(0,buf,sizeof(buf)); //默认情况下是阻塞的,当执行到这里的时候,程序会卡住,等待键盘输入。如果输入字符,ret为 成功读取的字节数,errno=0。如果输入ctrl+d,则ret=0,errno=0。按ctrl+d并不能让read退出,需要自 己根据返回值,写判断语句让read退出。
//标准输入改为非阻塞之后,如果在read之前输入数据,read会正常读入。如果没有输入数据,read 不会卡着等待输入,而是继续运行,此时ret=-1,errno=11.
printf("ret=%d\n",ret);
printf("errno=%d\n",errno);
printf("buf=%s\n",buf);
return 0;
}
read(0)函数返回值(标准输入阻塞的情况):
1. >0 成功读取的字节数,errno=0。
2. =0 到达文件尾。读标准输入时,按crtl +d,相当于EOF信号。此时errno=0。
3. -1 发生错误,通过errno确定具体错误值。
read(0)标准输入非阻塞的情况:
1. >0 成功读取的字节数
2. =0 到达文件尾。在阻塞前输入ctrl+d,相当于EOF。此时errno=0.
3. -1 有可能发生错误,通过errno确定具体错误值。有可能因为没有输入数据,read没有阻塞的返回值,此时返回值是-1,errno=11.
recv(sfd)的返回值(网络描述符阻塞的情况):
1. >0 接收到数据大小
2. =0 正常连接关闭
3. -1 出错
对方没有发送数据,有没有关闭:则读端阻塞。
recv(sfd)的返回值(网络描述符非阻塞的情况):
1. >0 接收到数据大小
2. =0 正常连接关闭
3. -1 有可能是出错,通过errno确定具体错误值。有可能因为读取对应描述符,缓冲区为空,recv不阻塞,返回值是-1,errno=11(EAGAIN)
例子: 用epoll实现即时聊天且:1.针对客户端的每一次发送的数据,epoll_wait只触发一次。2.一次把缓冲区里面的数据一次读完。
//通过epoll来实现tcp即时通信:服务端 #define NUM 10 void change_noblock(int fd){ //将文件描述符的属性改为非阻塞 int status; status=fcntl(fd,F_GETFL); status=status|O_NONBLOCK; int ret=fcntl(fd,F_SETFL,status); if(-1==ret){ perror("fcntl"); return; } } int main(int argc,char* argv[]){ if(argc!=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2])); ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); if(-1==ret){ perror("bind"); return -1; } ret=listen(sfd,NUM); if(-1==ret){ perror("listen"); return -1; } int epfd=epoll_create(1); //创建epoll句柄 struct epoll_event event,evs[NUM+1]; event.events=EPOLLIN; event.data.fd=sfd; ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //在epfd中注册sfd if(-1==ret){ perror("epoll_ctl"); return -1; } event.events=EPOLLIN; event.data.fd=0; ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event); //注册标准输入 if(-1==ret){ perror("epoll_ctl"); return -1; } int i; int new_fd; char buf[10]; int n; while(1){ memset(evs,0,sizeof(evs)); ret=epoll_wait(epfd,evs,NUM+1,-1); //监听epfd if(ret >0){ for(i=0;i<ret;i++){ if(evs[i].events == EPOLLIN &&evs[i].data.fd == sfd){ //新请求 new_fd=accept(sfd,NULL,NULL); printf("new_fd=%d\n",new_fd); event.events=EPOLLIN|EPOLLET;//边沿触发的是否可读 change_noblock(new_fd); //改变描述符的属性为非阻塞 event.data.fd=new_fd; epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event); //注册 } if(evs[i].events == EPOLLIN &&evs[i].data.fd == 0){ //输入 memset(buf,0,sizeof(buf)); n=read(0,buf,sizeof(buf)); if(n>0){ send(new_fd,buf,strlen(buf)-1,0); }else if(n==0){ //在新的一行输入ctrl+d,表示EOF printf("bye\n"); event.events=EPOLLIN; event.data.fd=new_fd; epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event); close(new_fd); } } if(evs[i].events == EPOLLIN &&evs[i].data.fd == new_fd){ while(1){ //通过循环直到把数据读空 memset(buf,0,sizeof(buf)); n=recv(new_fd,buf,sizeof(buf),0); if(n>0){ printf("%s",buf); }else if(n == -1 && errno == EAGAIN){ //读空了 break; }else if(n==0){ //对方关闭连接了 printf("bye\n"); event.events=EPOLLIN; event.data.fd=new_fd; epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event); close(new_fd); break; } } printf("\n"); //读完之后打印换行 } } } } return 0; } | //通过epoll来实现tcp即时通信:客户端 (未改进) int main(int argc,char** argv){ if(argc !=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2]));//一定要用htons ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); if(-1==ret){ perror("connect"); return -1; } int epfd=epoll_create(1); struct epoll_event event,evs[2]; event.events=EPOLLIN; event.data.fd=sfd; ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); if(-1==ret){ perror("epoll_ctl"); return -1; } event.events=EPOLLIN; event.data.fd=0; ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event); if(-1==ret){ perror("epoll_ctl"); return -1; } int i; char buf[128]; int n; while(1){ memset(evs,0,sizeof(evs)); ret=epoll_wait(epfd,evs,2,-1); if(ret>0){ for(i=0;i<ret;i++){ if(evs[i].events == EPOLLIN && evs[i].data.fd == 0){ memset(buf,0,sizeof(buf)); n=read(0,buf,sizeof(buf)); if(n==0){ printf("bye\n"); close(sfd); return 0; } n=send(sfd,buf,strlen(buf)-1,0); if(-1==n){ perror("send"); return -1; } } if(evs[i].events== EPOLLIN && evs[i].data.fd ==sfd){ memset(buf,0,sizeof(buf)); n=recv(sfd,buf,sizeof(buf),0); if(n > 0){ printf("%s\n",buf); }else if(n==0){ printf("bye\n"); close(sfd); return 0; } } } } } return 0; } |