1.epoll的两种工作模式介绍
epoll的两种模式ET和LT:
LT模式(水平触发):是缺省的工作方式,并且同时支持block和non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪,然后可以对这个就绪的fd进行io操作,如果你不做任何操作,内核还会继续通知你的。(只要缓冲区有数据就会一直通知)
ET模式(边沿触发):是高速工作方式,只支持no-block socket。在这种模式下,当文件描述符从未就绪变为就绪时,内核会通过epoll告诉你。然后,内核会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,知道你做了某些操作导致那个文件描述符不再为就绪状态了。(缓冲区有数据只会通知一次,之后再有数据才回通知)
2.测试ET模式和LT模式的区别
源代码(wrap相关文件在文章里):
#include"wrap.h"
#include<stdio.h>
#include<sys/epoll.h>
#include<errno.h>
#include<ctype.h>
int main(){
int lfd;
int ret;
int n;
int epfd;
int i;
int nready;
int cfd;
int sockfd;
char buf[1024];
socklen_t socklen;
struct sockaddr_in svraddr;
struct epoll_event ev;
struct epoll_event events[1024];
//创建socke
lfd=socket(AF_INET,SOCK_STREAM,0);
//设置端口复用
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
//绑定
bzero(&svraddr,sizeof(svraddr));
svraddr.sin_addr.s_addr=htonl(INADDR_ANY);
svraddr.sin_port=htons(8888);
svraddr.sin_family=AF_INET;
Bind(lfd,(struct sockaddr*)&svraddr, sizeof(svraddr));
//设置监听
Listen(lfd,128);
//创建一个epoll树
epfd=epoll_create(1024);
if(epfd<0){
perror("create epoll error");
return -1;
}
//将监听文件描述符上epoll树
ev.events=EPOLLIN;
ev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
while (1)
{
nready=epoll_wait(epfd,events,1024,-1);
if(nready<0){
perror("epoll_wait error");
if(errno==EINTR){
continue;
}
break;
}
for(i=0;i<nready;i++){
sockfd=events[i].data.fd;
//有客户端请求到来
if(sockfd==lfd){
cfd=Accept(lfd,NULL,NULL);
//将cfd对应的事件上树
ev.data.fd=cfd;
ev.events=EPOLLIN;//可读事件
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
continue;
}
//有客户端发送数据到来
memset(buf,0x00,sizeof(buf));
//n=Read(sockfd,buf,sizeof(buf));
n=recv(sockfd,buf,2,0);
if(n<=0){
printf("n==%d, buf==%s\n",n,buf);
Close(sockfd);
//将sockfd对应的事件节点从epoll树上删除
epoll_ctl(cfd,EPOLL_CTL_DEL,sockfd,NULL);
perror("read error or client error");
continue;
}else{
printf("n==%d, buf==%s\n",n,buf);
for(int k=0;k<n;k++){
buf[k]=toupper(buf[k]);
}
//Write(sockfd,buf,n);
send(sockfd,buf,n,0);
}
}
}
Close(epfd);
Close(lfd);
return 0;
}
可以发现执行了四次,epoll默认情况下为水平模式,在这种模式下,若数据一次性没有读完,缓冲区还有可读数据,则epoll_wait还会再次通知。如设置成ET模式:
源代码:
#include"wrap.h"
#include<stdio.h>
#include<sys/epoll.h>
#include<errno.h>
#include<ctype.h>
int main(){
int lfd;
int ret;
int n;
int epfd;
int i;
int nready;
int cfd;
int sockfd;
char buf[1024];
socklen_t socklen;
struct sockaddr_in svraddr;
struct epoll_event ev;
struct epoll_event events[1024];
//创建socke
lfd=socket(AF_INET,SOCK_STREAM,0);
//设置端口复用
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
//绑定
bzero(&svraddr,sizeof(svraddr));
svraddr.sin_addr.s_addr=htonl(INADDR_ANY);
svraddr.sin_port=htons(8888);
svraddr.sin_family=AF_INET;
Bind(lfd,(struct sockaddr*)&svraddr, sizeof(svraddr));
//设置监听
Listen(lfd,128);
//创建一个epoll树
epfd=epoll_create(1024);
if(epfd<0){
perror("create epoll error");
return -1;
}
//将监听文件描述符上epoll树
ev.events=EPOLLIN;
ev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
while (1)
{
nready=epoll_wait(epfd,events,1024,-1);
if(nready<0){
perror("epoll_wait error");
if(errno==EINTR){
continue;
}
break;
}
for(i=0;i<nready;i++){
sockfd=events[i].data.fd;
//有客户端请求到来
if(sockfd==lfd){
cfd=Accept(lfd,NULL,NULL);
//将cfd对应的事件上树
ev.data.fd=cfd;
ev.events=EPOLLIN | EPOLLET;//可读事件
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
continue;
}
//有客户端发送数据到来
memset(buf,0x00,sizeof(buf));
//n=Read(sockfd,buf,sizeof(buf));
n=recv(sockfd,buf,2,0);
if(n<=0){
printf("n==%d, buf==%s\n",n,buf);
Close(sockfd);
//将sockfd对应的事件节点从epoll树上删除
epoll_ctl(cfd,EPOLL_CTL_DEL,sockfd,NULL);
perror("read error or client error");
continue;
}else{
printf("n==%d, buf==%s\n",n,buf);
for(int k=0;k<n;k++){
buf[k]=toupper(buf[k]);
}
//Write(sockfd,buf,n);
send(sockfd,buf,n,0);
}
}
}
Close(epfd);
Close(lfd);
return 0;
}
若将epoll模式设置为ET模式,若读数据的时候一次性没有读完,则epoll_wait不再通知,知道下次有新的数据发来。
3.ET模式下的非阻塞模式
在ET模式下,需要循环读完数据,知道读完数据,但是读完数据会阻塞;若要一次性读完数据,还需要将通信文件描述符设置为非阻塞模式。
源代码:
#include"wrap.h"
#include<stdio.h>
#include<sys/epoll.h>
#include<errno.h>
#include<ctype.h>
#include<fcntl.h>
int main(){
int lfd;
int ret;
int n;
int epfd;
int i;
int nready;
int cfd;
int sockfd;
char buf[1024];
socklen_t socklen;
struct sockaddr_in svraddr;
struct epoll_event ev;
struct epoll_event events[1024];
//创建socke
lfd=socket(AF_INET,SOCK_STREAM,0);
//设置端口复用
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
//绑定
bzero(&svraddr,sizeof(svraddr));
svraddr.sin_addr.s_addr=htonl(INADDR_ANY);
svraddr.sin_port=htons(8888);
svraddr.sin_family=AF_INET;
Bind(lfd,(struct sockaddr*)&svraddr, sizeof(svraddr));
//设置监听
Listen(lfd,128);
//创建一个epoll树
epfd=epoll_create(1024);
if(epfd<0){
perror("create epoll error");
return -1;
}
//将监听文件描述符上epoll树
ev.events=EPOLLIN;
ev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
while (1)
{
nready=epoll_wait(epfd,events,1024,-1);
if(nready<0){
perror("epoll_wait error");
if(errno==EINTR){
continue;
}
break;
}
for(i=0;i<nready;i++){
sockfd=events[i].data.fd;
//有客户端请求到来
if(sockfd==lfd){
cfd=Accept(lfd,NULL,NULL);
//将cfd对应的事件上树
ev.data.fd=cfd;
ev.events=EPOLLIN | EPOLLET;//可读事件
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
//将cfd设置为非阻塞
int flag =fcntl(cfd,F_GETFL);
flag |=O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
continue;
}
//有客户端发送数据到来
memset(buf,0x00,sizeof(buf));
while(1){
//n=Read(sockfd,buf,sizeof(buf));
n=Read(sockfd,buf,2);
//读完数据
if(n==-1){
printf("read over, n=%d\n",n);
break;
}
//对方关闭连接或者异常
if(n==0||(n<0&&n!=-1)){
printf("n==%d, buf==%s\n",n,buf);
Close(sockfd);
//将sockfd对应的事件节点从epoll树上删除
epoll_ctl(cfd,EPOLL_CTL_DEL,sockfd,NULL);
break;
}else{
printf("n==%d, buf==%s\n",n,buf);
for(int k=0;k<n;k++){
buf[k]=toupper(buf[k]);
}
Write(sockfd,buf,n);
}
}
}
}
Close(epfd);
Close(lfd);
return 0;
}