epoll是内核2.6版本提出来的,比poll和select更加灵活,而且没有文件描述符的限制
int epoll_create(int size);//创建一个epoll句柄,size告诉内核这个监听的数目一共多大,成功返回一个文件文件符的句柄,失败返回-1.
int epoll_ctl(int epfd, intopt ,int fd, struct epoll_event *event);//epoll事件注册
第一个参数epfd是epoll_create的返回值
第二个: EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL
第三个是要监听的fd
第四个是告诉内核要监听的是什么事件
struct epoll_event {
__uint32_t events;
epoll_data_t data;
}
events可以是以下几个宏的集合
EPOLLIN: 标示对应的文件描述符刻度(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可写
EPOLLPRI:表示对应的文件描述符有紧急的数据可写(表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂冠
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还要继续监听这个socket的话
,需要再次把这个socket加入到EPOLL队列
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
等待事件的产生,类似于select的调用,参数events用来从内核中得到时间的集合,maxevents告知内核这个event有多大,
max_events的值不能大于epoll_create()时候的size,timeout是超时间
如果返回0,表示超时
如果返回-1,表示epoll出错,退出(错误原因可用errno查看)
如果返回非零整数,表示是需要处理的事件数量
工作模式 LT(level triger)和ET(edge trigger),LT模式是默认模式,
区别:
LT 模式只要有未处理的数据就会有通知(如缓冲区中有数据,第一次没有读取完,
那么下次还会受到通知)
ET 模式只有状态变化才会发出通知(如文件描述符有不可读变为可读时候,就会收到通知,
要一次性吧数据处理完,否则就算缓冲区里面还有数据,下次也不会通知)
**ET模式下可以减少文件描述符返回的数量,也能减少epoll相关的系统调用,
但是与LT模式比较来说,性能差异不大,ET模式逻辑复杂,出错率高,所以建议使用LT模式**
获得通知的条件:
与select/poll类似,当以下的条件发生的时候,epoll监控的相应的文件描述符会变得可读/写
取到数据的长度
2)当读半部分关闭的时候(也就是对端发送了一个FIN分节过来),此时虽然可读,但是调用recv的时候返回的是
0(腾讯面试被问过),这时候服务端可以把相应的socket关闭
3)套接字发生错误,此时recv返回的是-1,可以通过errno获得错误原因,或者通过SOL_ERROR调用getsockopt获取错误并处理
4)连接完成队列不为空,那么accpet肯定不会阻塞
对于发送,也是类似的条件,但是要注意,如果一个套接字发送的时候,如果写半部分部分关闭,对这样的套接字发送数据会产生SIGPIPE信号,系统对这个信号的默认处理是结束进程
#include
#include
#include
#include
#include
#include
#include
#include
const int MaxFd = 2048;
int main() {
int epollfd;
epoll_event events[MaxFd], ev;
sockaddr_in srvAddr;
sockaddr_in cliAddr;
int fd = socket(AF_INET, SOCK_STREAM, 0);
srvAddr.sin_family = AF_INET;
srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
srvAddr.sin_port = htons(6000);
if(bind(fd, (sockaddr*)&srvAddr, sizeof(sockaddr)) < 0) {
fprintf(stderr, "bind Error:%s\n", strerror(errno));
return 1;
}
if(listen(fd, 16) < 0) {
fprintf(stderr, "listen Error: %s\n", strerror(errno));
return 1;
}
epollfd = epoll_create(MaxFd);
if(epollfd < 0) {
fprintf(stderr, "epoll_create Error: %s\n", strerror(errno));
return 1;
}
ev.events = EPOLLIN;
ev.data.fd = fd;
if( epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
fprintf(stderr, "epoll_ctl Error: %s\n", strerror(errno));
return 1;
}
for(;;) {
int nfds = epoll_wait(epollfd, events, MaxFd, 10);
if(nfds < 0) {
fprintf(stderr, "epoll_wait Error: %s\n", strerror(errno));
return 1;
}
for(int i = 0; i < nfds; i++) {
if(events[i].data.fd == fd) {
socklen_t len;
int newfd = accept(fd, (sockaddr*)&cliAddr, &len);
if(newfd < 0) {
fprintf(stderr, "accept Error: %s\n", strerror(errno));
}
ev.data.fd = newfd;
ev.events = EPOLLIN;
fcntl(newfd, F_SETFL, O_NONBLOCK);
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, newfd, &ev) < 0) {
fprintf(stderr, "epoll_ctl Error: %s\n", strerror(errno));
return -1;
}
printf("new connection: %s:%d current connetion:%d\n", inet_ntoa(cliAddr.sin_addr), htons(cliAddr.sin_port), count);
} else{
if(events[i].events & EPOLLIN) {
char buf[256];
memset(buf, 0, sizeof(buf));
int ret = recv(events[i].data.fd, buf, sizeof(buf), 0);
if(ret == 0) {//client close
ev.data.fd = events[i].data.fd;
ev.events = EPOLLIN;
if( epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, &ev) < 0) {
fprintf(stderr, "epoll_ctl Error: %s\n", strerror(errno));
}
close(events[i].data.fd);
}else if(ret < 0) {
ev.data.fd = events[i].data.fd;
ev.events = EPOLLIN;
if( epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, &ev) < 0) {
fprintf(stderr, "epoll_ctl Error: %s\n", strerror(errno));
}
close(events[i].data.fd);
}
else
printf("Message: %s\n", buf);
}
}
}
}
}