epoll简介
a) epoll是对poll的改进;是linux2.6下性能最好的多路I/O就绪通知方法。相对于select和poll来说,epoll更加灵活并且不存在描述符限制。epoll使用一个文件描述符对其他的描述符进行管理;将用户所感兴趣的事件到内核进行注册,因此只需从用户态到内核态的一次copy,这与select、poll不同。而select、poll每次调用都要传递用户所有监控的socket给select/poll系统调用,这就意味着将用户态的socket列表拷贝到内核态。
b) 内核对于文件描述符集合的管理采用了红黑树,通过epoll_ctl对 红黑树上的文件描述符进行高效的管理;有效的对重复的注册信息 去重。
c) 此外,epoll维护一个双链表,用于存储已经就绪的文件描述符信息。当epoll_wait调用时,仅仅 这个list中是否存在数据即可。有数据就返回,没有数据就sleep,等timeout时即使链表没有数据也需要返回。通常情况下,监控大量的句柄但每次只有少量的就绪的句柄返回,因此只有少量的文件描述符从内核态拷贝到用户态。
函数介绍
int epoll_create(int)
int epoll_wait(int epollfd,struct epoll_event *events,int)
int epoll_ctl(int epollfd,int OPTION,int fd,struct epoll_event *ev)
struct epoll_event
{
int events;
epoll_data_t data;
};
typedef union epoll_data
{
void *ptr ;
int fd ;
__uint32_t u32 ;
__uint64_t u64 ;
}epoll_data_t;
OPTION : EPOLL_CTL_ADD/EPOLL_CTL_DEL/EPOLL_CTL_MOD
水平触发与边缘触发
a) 水平触发
LT(level triggered)是 epoll缺省工作方式,并且同时支持block 和no-block socket. 即:内核通知epoll_wait某个文件描述符已经就绪,然后你对此文件描述符进行处理;如果你不做处理的话,内核会继续通知 epoll_wait对此文件描述符进行处理;此方法不易出错。
b) 边缘触发
ET(edge triggered)是一种比较 高速的工作方式,只支持no-block socket. 相对于LT效率高。与LT的 区别是当内核通知epoll_wait一次后,不论用户对此文件描述符处理与否内核将不会对此文件描述符的事件再一次通知用户进行处理。也就是说,只通知用户一次。而LT会持续通知,直到处理为止。
c) 3. 重点
- 对于监听的sockfd来说,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上,如果非要使用边缘触发,只能使用while来循环accept();
- 对于读写connfd,水平触发模式下,阻塞和非阻塞效果是一样的,不过为了防止特殊情况还是建议设置非阻塞;
- 对于读写connfd,边缘触发模式下,必须使用非阻塞IO,并要求一次性读写完数据。
服务端代码
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/epoll.h>
#include<sys/types.h>
#include<sys/socket.h>
#define IP "127.0.0.1"
#define PORT 9090
#define MAXLINE 1024
#define MAXLISTEN 5
/*
@funcationname: createAndBind
@description:create socket and bind the addresss
@return
succ: the valid of socket
fail: -1
*/
int createAndBind()
{
int listenfd ;
struct sockaddr_in servAddr ;
bzero(&servAddr,sizeof(servAddr)) ;
listenfd = socket(AF_INET,SOCK_STREAM,0) ;
if (listenfd < 0)
{
printf("%s %d error create the listen socket,err:%d\n",__FILE__,__LINE__,errno) ;
return -1 ;
}
servAddr.sin_family = AF_INET ;
inet_pton(AF_INET,IP,&servAddr.sin_addr) ;
servAddr.sin_port = htons(PORT);
int ret = bind(listenfd,( struct sockaddr *)&servAddr,sizeof(servAddr)) ;
if (ret < 0)
{
printf("%s %d, error bind the address,err:%d\n",__FILE__,__LINE__,errno) ;
return -1 ;
}
ret = listen(listenfd,MAXLISTEN) ;
if (ret < 0)
{
printf("%s %d, error listen \n",__FILE__,__LINE__) ;
return -1 ;
}
int bReuseAddr = 1 ;
ret = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(const char *)&bReuseAddr,sizeof(bReuseAddr)) ;
if (ret < 0)
{
printf("%s %d,error setsockopt SO_REUSEADDR\n",__FILE__,__LINE__) ;
return -1 ;
}
return listenfd ;
}
/*
@functionname: epollAddEvent
@decription: add the fd's event
@input :
epollfd : epollfd
clientfd: client socket
state : the event need to add to clientfd
@return
succ : 0
fail : -1
*/
int epollAddEvent(int epollfd,int clientfd,int state)
{
struct epoll_event event ;
event.data.fd = clientfd ;
event.events = state ;
return epoll_ctl(epollfd,EPOLL_CTL_ADD,clientfd,&event) ;
}
/*
@functionname: epollModEvent
@decription: Modify the fd's event
@input :
epollfd : epollfd
clientfd: client socket
state : the event need to modify clientfd
@return
succ : 0
fail : -1
*/
int epollModEvent(int epollfd,int clientfd,int state)
{
struct epoll_event event ;
event.data.fd = clientfd ;
event.events = state ;
return epoll_ctl(epollfd,EPOLL_CTL_MOD,clientfd,&event) ;
}
/*
@functionname: epollDelEvent
@decription: Delete the fd's event
@input :
epollfd : epollfd
clientfd: client socket
state : the event need to delete from clientfd
@return
succ : 0
fail : -1
*/
int epollDelEvent(int epollfd,int clientfd,int state)
{
struct epoll_event event ;
event.data.fd = clientfd ;
event.events = state ;
return epoll_ctl(epollfd,EPOLL_CTL_DEL,clientfd,&event) ;
}
/*
@functionname: handlAccept
@decription: handle the listen socket
@input :
epollfd : epollfd
listenfd: the listen socket
@return
succ : 0
fail : -1
*/
int handleAccept(int epollfd,int listenfd)
{
int connfd = -1 ;
struct sockaddr_in cliAddr ;
socklen_t cliAddrLen ;
int ret = -1 ;
//cliAddrLen = sizeof(cliAddr) ;
memset(&cliAddr,0,sizeof(cliAddr)) ;
connfd = accept(listenfd,(struct sockaddr*)&cliAddr,&cliAddrLen) ;
if (connfd < 0)
{
printf("%s %d,error accept .err:%d\n",__FILE__,__LINE__,errno) ;
return -1 ;
}
else
{
printf("%s,%d accept connfd is ok\n",__FILE__,__LINE__) ;
ret = epollAddEvent(epollfd,connfd,EPOLLIN) ;
if (ret < 0)
{
printf("%s %d, epollAddEvent is error\n",__FILE__,__LINE__) ;
return -1 ;
}
return 0 ;
}
}
char buf[MAXLINE] ;
int doRead(int epollfd,int cliConn)
{
int nread ;
memset(buf,0,MAXLINE) ;
nread = read(cliConn,buf,MAXLINE) ;
if (nread < 0) //something is error
{
if (EINTR == errno)
{
printf("%s %d read is interrupt\n",__FILE__,__LINE__) ;
return -1 ;
}
else
{
printf("%s %d,error read,err:%d\n",__FILE__,__LINE__,errno) ;
close(cliConn) ;
epollDelEvent(epollfd,cliConn,EPOLLIN) ;
return -1 ;
}
}
else if (nread==0) // the connection is close
{
printf("%s %d,the socket is closed ,err:%d\n",__FILE__,__LINE__,errno) ;
close(cliConn) ;
epollDelEvent(epollfd,cliConn,EPOLLIN) ;
return 0 ;
}
else // read the data
{
buf[nread] = '\0' ;
printf("recv message:%s\n",buf) ;
epollModEvent(epollfd,cliConn,EPOLLOUT);
return 0 ;
}
}
int doWrite(int epollfd,int cliConn)
{
int nWrite ;
nWrite = write(cliConn,buf,strlen(buf)) ; // you can use writen
if (nWrite < 0) //error
{
if(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
{
return -1 ;
}
else
{
printf("%s %d write is error,%d\n",__FILE__,__LINE__,errno) ;
close(cliConn) ;
epollDelEvent(epollfd,cliConn,EPOLLOUT) ;
return -1;
}
}
else //write success
{
printf("%s,%d,write is ok\n",__FILE__,__LINE__) ;
epollModEvent(epollfd,cliConn,EPOLLIN) ;
return 0 ;
}
}
/*
@functionname: doEPoll
@decription: the main loop of epoll
@input :
epollfd : epollfd
listenfd: listen socket
@return
succ : 0
fail : -1
*/
int doEpoll(int epollfd,int listenfd)
{
struct epoll_event events[MAXLINE] ;
int ret = -1 ;
int i = 0 ;
for(;;)
{
ret = epoll_wait(epollfd,events,MAXLINE,-1) ; // it is not weekup util someone's event is active or the timeout is reached!
if (ret < 0)
{
if (EINTR == errno)
{
printf("%s %d, epoll is interrupt:%d\n",__FILE__,__LINE__,errno) ;
continue ;
}
else
{
close(epollfd) ;
return -1 ;
}
}
else if (ret == 0) //timeout
{
printf("%s %d,epoll is timeout\n",__FILE__,__LINE__) ;
continue ;
}
else //ok
{
for (i = 0 ;i<ret ; i++)
{
if( (events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP))
{
printf("%s %d, error socket\n",__FILE__,__LINE__) ;
close(events[i].data.fd) ;
continue ;
}
else if( (events[i].data.fd == listenfd) && (events[i].events & EPOLLIN))
{
handleAccept(epollfd,listenfd) ;
}
else if( events[i].events & EPOLLIN)
{
doRead(epollfd,events[i].data.fd) ;
}
else if (events[i].events & EPOLLOUT)
{
doWrite(epollfd,events[i].data.fd) ;
}
} // end for ret
} // end if ret
} //end for(;;)
}
int main()
{
int listenfd ;
int epollfd ;
epollfd = epoll_create(1000) ;
if (epollfd < 0)
{
printf("%s,%d error create epollfd:%d\n",__FILE__,__LINE__,errno) ;
exit(-1) ;
}
listenfd = createAndBind() ;
if (listenfd < 0)
{
exit(-1) ;
}
int ret = epollAddEvent(epollfd,listenfd,EPOLLIN) ;
if (ret < 0)
{
printf("%s %d, error add epoll event listenfd,err:%d\n",__FILE__,__LINE__,errno) ;
exit(-1) ;
}
doEpoll(epollfd,listenfd) ;
return 0 ;
}
客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#define IP "127.0.0.1"
#define PORT 9090
#define MAXLINE 1024
int handleConnection(int sockfd)
{
char buf[MAXLINE] ="hello Mr.Luo!" ;
int ret = write(sockfd,buf,strlen(buf)) ;
if (ret < 0)
{
printf("error:%d\n",errno) ;
close(sockfd) ;
return -1 ;
}
ret = read(sockfd,buf,MAXLINE) ;
buf[ret] = '\0' ;
printf("recv message :%s\n",buf) ;
return 0 ;
}
int main()
{
int sockfd ;
struct sockaddr_in servAddr ;
sockfd = socket(AF_INET,SOCK_STREAM,0) ;
bzero(&servAddr,sizeof(servAddr) ) ;
servAddr.sin_family= AF_INET ;
inet_pton(AF_INET,IP,&servAddr.sin_addr) ;
servAddr.sin_port = htons(PORT) ;
int ret = connect(sockfd,(struct sockaddr*)&servAddr,sizeof(servAddr)) ;
if (ret < 0)
{
printf("close the socket,when connect is failed\n") ;
close(sockfd) ;
exit(-1) ;
}
handleConnection(sockfd) ;
close(sockfd) ;
return 0 ;
}