Linux中IO多路复用接口epoll的使用
在C语言的IO模型中,大家可能接触和学习过selelct、 poll等实现IO多路复用的函数接口,这些接口在windows下或者linux下都是支持的,但是分析select和poll就会发现他们在使用的过程中存在一些问题,比如 select函数可以检测的文件描述符的个数有限制,多支持1024个,而poll呢在检测大量的描述符的时候占用CPU的时间会显著的增加,针对他们存在的这些问题呢,我们下边来学习epoll函数接口来弥补他们的不足。
epoll接口是linux中特有的,其他平台是不支持的。epoll接口可以检测的描述符的个数要远大于select,而且使用的灵活性更高。接下来我们来分析一下epoll的函数API,它主要包括三个函数:epoll_create、epoll_ctl、epoll_wait。
nt epoll_create(int size);
功能:创建一个epoll的标示符。
参数:size 现在已经无用,并不表示检测的大值。
返回值:就是获得的epoll的标示符。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制需要检测的文件描述符。
参数:epfd: epoll_create得到的标识符。
op:表示此次调用要执行的操作,包括添加描述符、删除和修改属性。
EPOLL_CTL_ADD 添加描述符
EPOLL_CTL_DEL 删除描述符
EPOLL_CTL_MOD 修改描述符触发的属性
fd: 要检测的文件描述符的值
event: 执行要检测的描述符的特性,结构体成员如下
struct epoll_event
{
uint32_t events; /* Epoll events ,指定描述符被检测的条件*/
epoll_data_t data; /* User data variable */
};
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待就绪的文件描述符
参数:epfd: epoll_create得到的标识符
events: 用来存储就绪的文件描述符状态的结构体数组指针
maxevents: 指定要检测的描述符的大个数
timeout: 指定超时检测的时间(如果不指定超时,将该参数指定为-1即可)
返回值:就绪的文件描述符的个数。
下边是一个使用epoll接口实现的基于tcp协议的服务器代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define N 1000
int main(int argc, char *argv[])
{
int listenfd, connectfd;
struct sockaddr_in serveradd, clientadd;
unsigned int size = sizeof(clientadd);
int ret, i = 0;
ssize_t bytes;
int epfd ;
/*
*even用来添加新的描述符
*even_list用来接收回传的准备好的描述符
*/
struct epoll_event even, even_list[N];
char buf[128] = {0};
bzero(even_list, sizeof(struct epoll_event) * N);
/*
*将所有的结构体中的描述符都初始化为-1.
*/
for (i = 0; i < N; i++) {
even_list[i].data.fd = -1;
}
bzero(&serveradd, sizeof(serveradd));
bzero(&clientadd, sizeof(clientadd));
serveradd.sin_family = AF_INET;
serveradd.sin_port = htons(9999);
serveradd.sin_addr.s_addr = htonl(INADDR_ANY);
signal(SIGCHLD, SIG_IGN);
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("fail to create socket");
return -1;
}
if (bind(listenfd, (struct sockaddr*)&serveradd, sizeof(serveradd)) < 0) {
perror("fail to bind the server!");
close(listenfd);
return -1;
}
if (listen(listenfd, 20) < 0) {
perror("fail to listen");
close(listenfd);
return -1;
}
if ((epfd = epoll_create(1)) < 0) {
perror("fail to epoll_create");
close(listenfd);
return -1;
}
even.events = EPOLLIN;
even.data.fd = listenfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &even) < 0) {
perror("fail to epoll_ctl");
close(listenfd);
close(epfd);
return -1;
}
while (1) {
/*返回准备好的描述符的个数,
*准备好的描述符被存放在even_list中
*/
ret = epoll_wait(epfd, even_list, N, -1);
if (ret < 0) {
perror("fail to epoll_wait");
continue;
}
printf("ret = %d\n", ret);
//遍历准备好的描述符
for (i = 0; i < ret; i++) {
if (even_list[i].events == EPOLLIN) {
if (even_list[i].data.fd == listenfd) {
connectfd = accept(listenfd, (struct sockaddr*)&clientadd, &size);
printf("client add: %s, port :%d\n", inet_ntoa(clientadd.sin_addr),
ntohs(clientadd.sin_port));
even.events = EPOLLIN;
even.data.fd = connectfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, connectfd, &even);
} else {
bytes = recv(even_list[i].data.fd, buf, sizeof(buf), 0);
if (bytes == 0) {
close(even_list[i].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, even_list[i].data.fd, &even_list[i]);
//将不再被监听的描述符移除之后,重新将该下标的描述符赋值为-1
even_list[i].data.fd = -1;
even_list[i].events = 0;
}
sprintf(buf, "%d bytes has recv!\n", bytes);
send(even_list[i].data.fd, buf, sizeof(buf), 0);
}
}
}
}
close(listenfd);
return 0;
}