以下内容是参考华清远见《linux/unix系统编程手册》对epoll的一个个人总结,是我在华清远见比较全面的总结。
一、epoll的优点
同I/O多路复用和信号驱动I/O一样,linux的epoll API可以检查多个文件描述符上的I/O就绪状态。epoll API的主要优点
1.当有大量的文件描述符需要检查时,epoll的性能延展性比select()和epoll(高很多)
2.epoll API既支持水平触发也支持边缘触发,与之相反,select和poll只支持水平触发,而信号驱动I/O只支持边缘触发
3.可以避免复杂的信号处理流程(比如信号队列溢出时的处理)
4.灵活性高,可以指定我们希望检查的时间类型
二、epoll系统调用组成
1.epoll_create()创建一个epoll实例,返回代表该实例的文件描述符
2.epoll_ctl()操作同epoll实例相关联的兴趣列表
3.epoll_wait()返回与epoll实例相关联的就绪列表中的成员
#include
int epoll_create(int size);
功能:创建一个新的epoll实例,其对应的兴趣列表初始化为空
参数:size 想要检查文件描述符的个数(linux2.6.8之后该参数不再使用)
返回值:成功文件描述符,失败-1
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:修改epoll的兴趣列表
参数:epfd epoll_create的返回值
fd 要修改的文件描述符(可以是无名管道、有名管道、套接字、消息队列、终端、设备等,但是不能是普通文件或目录的文件描述符)
op:
EPOLL_CTL_ADD 添加fd到兴趣列表
EPOLL_CTL_MOD 修改已经注册的fd的监听事件;
EPOLL_CTL_DEL 从epfd中删除一个fd
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 */
};
常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 表示对应的文件描述符有事件发生;
返回值:成功0 失败-1
#include
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能:返回epoll实例中处于就绪态的文件描述符信息,单个epoll_wait()调用能返回多个就绪态文件描述符的信息。
参数:epfd epoll实例
events 存放文件描述符的信息
maxevents 文件描述符的大个数
timeout -1 阻塞等待
0 非阻塞
>0 阻塞的大时间
返回值:成功0 失败-1
三、网络中的应用实例
1.net.h
#ifndef _NET_H_
#define _NET_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_EVENTS 500
#endif
2.server.c
#include "head.h"
int sockfd, acceptfd,listenfd;
struct sockaddr_in serveraddr, clientaddr;
socklen_t len = sizeof(serveraddr);
struct epoll_event ev, events[MAX_EVENTS];
int rv,i;
int epollfd,fds;
int tcp_create_socket(void)
{
//1、设定基于TCP通信的标准
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("fail to socket");
return -1;
}
bzero(&serveraddr, len);
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi("8000"));//端口号
serveraddr.sin_addr.s_addr = inet_addr("192.168.8.199"); //IP地址
//2、绑定IP地址和端口号
if(bind(sockfd, (struct sockaddr*)&serveraddr, len) < 0)
{
perror("fail to bind");
close(sockfd);
return -1;
}
//3、设置监听描述符的个数
if(listen(sockfd, 5) < 0)
{
perror("fail to listen");
close(sockfd);
return -1;
}
return sockfd;
}
int mz_process_data(int acceptfd)//数据处理
{
int bytes = 0;
char buf[100];
bytes = recv(acceptfd, buf, 100, 0);
printf("----------------n");
if(bytes < 0)
{
perror("recv error");
return -1;
}
if(bytes == 0) //说明客户端放弃链接
{
return -2;
}
printf("client:%sn", buf);
return 0;
}
int main(int argc, const char *argv[])
{
epollfd = epoll_create(MAX_EVENTS);//创建对象
if(epollfd < 0)
{
perror("fail to epoll!");
return -1;
}
listenfd = tcp_create_socket();
fcntl(listenfd,F_SETFL, O_NONBLOCK);//设置非阻塞
ev.data.fd = listenfd;
ev.events = EPOLLIN;
rv = epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);//加入监听事件
if (rv < 0)
{
perror("epoll_ctl err:");
return -1;
}
while(1)
{
fds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if(fds < 0)
{
perror("epoll_wait error");
return -1;
}
//轮寻被激活的套接子
for(i = 0; i < fds; i++)
{
if(events.data.fd == listenfd) //判断是否是监听的对象(描述符)
{
acceptfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
if(acceptfd < 0)
{
perror("accept error");
continue;
}
ev.data.fd = acceptfd;
ev.events = EPOLLIN | EPOLLET;
//将链接的客户端添加到关注的事件中去
epoll_ctl(epollfd, EPOLL_CTL_ADD, acceptfd, &ev);
continue;
}
else
{
rv = mz_process_data(events.data.fd);
if(rv == -2) //客户端放弃链接,将描述符从事件中清除
{
epoll_ctl(epollfd, EPOLL_CTL_DEL, events.data.fd, &ev);
close(events.data.fd);
continue;
}
}
}
}
return 0;
}
3.client.c
#include "head.h"
#define N 32
int main(int argc, const char *argv[])
{
int sockfd;
struct sockaddr_in serveraddr;
socklen_t len = sizeof(serveraddr);
char buf[N] = {0};
//1、设定基于TCP通信的标准
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("fail to socket");
return -1;
}
bzero(&serveraddr, len);
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi("8000"));//端口号
serveraddr.sin_addr.s_addr = inet_addr("192.168.8.199"); //IP地址
//2、绑定IP地址和端口号
if(connect(sockfd, (struct sockaddr*)&serveraddr, len) < 0)
{
perror("fail to connect");
close(sockfd);
return -1;
}
//客户端用来发送数据
while(1)
{
fgets(buf, N, stdin);
buf[strlen(buf) - 1] = '