epoll函数是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大的差异。首先,epoll函数使用一组函数来完成任务,而不是单个函数。其次,epoll把用户关心的文件符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符集或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符使用如下的epoll_create函数来创建:
#include<epoll.h>
int epoll_create(int size)
size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大,该函数返回的文件描述符将用作其他所有的epoll系统调用的第一个参数,以指定要访问的内核事件表。
下面的函数用来操作epoll的内核事件表:
#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event * event)
fd参数是要操作的文件描述符,op参数指定操作类型。操作类型有如下3种:
(1)EPOLL_CTL_ADD,往事件表中注册fd上的事件。
(2)EPOLL_CTL_MOD,修改fd上的注册事件。
(3)EPOLL_CTL_DEL,删除fd上的注册事件。
event参数指定事件,它是epoll_event结构体指针类型。epoll_event的定义如下:
struct epoll_event
{
__uint32_t events; /* epoll事件*/
epoll_data_t data;/*用户数据*/
};
其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上“E”,比如epoll的数据可读事件是EPOLLIN。但epoll有两个额外的事件类型-------EPOLLET和EPOLLONESHOT。它们对于epoll的高效运作非常关键,我们将在后面讨论。data成员用于存储数据,其类型epoll_data_t定义如下:
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t ;
epoll_data_t 是一个联合体,其4个成员中使用最多的是fd,它指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但由于epoll_data_t是一个联合体,我们不能同时使用其ptr成员和fd成员,因此,如果要将文件描述符和用户数据关联起来,以实现快速的数据访问,只能使用其他手段,比如放弃epoll_data_t的fd成员,而在ptr指向的用户数据中包含fd。
epoll_ctl成功时返回0,失败时则返回-1并置errno。
epoll_wait函数:
epoll系列系统的主要接口是epoll_wait函数。它在一段超时时间内等待一组文件描述符上的事件,其原型如下:
#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event* event,int maxevents,int timeout)
该函数成功时返回就绪文件描述符的个数。timeout参回的含义与poll接口的timeout参数相同。maxevents参数指定最多监听多少个事件,它必须大于0。
epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只能用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率。
我们同样给出一个简单的例子,回射程序:
服务器:
#include"../unp.h"
#include"utili.h"
#include<stdlib.h>
int sock_bind(const char *ip, short port)
{
int fd;
//创建套接字
fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(port);
addrSer.sin_addr.s_addr = inet_addr(ip);
socklen_t addrlen = sizeof(struct sockaddr);
//绑定
bind(fd, (struct sockaddr*)&addrSer, addrlen);
return fd;
}
//客户端连接服务器
void handle_accept(int epollfd, int listenfd)
{
struct sockaddr_in addrCli;
int sockConn;
socklen_t addrlen = sizeof(struct sockaddr);
sockConn = accept(listenfd, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn == -1)
perror("accept");
else
{
printf("accept a new client:%s:%d\n",inet_ntoa(addrCli.sin_addr),addrCli.sin_port);
add_event(epollfd, sockConn, EPOLLIN);
}
}
//服务器读数据
void do_read(int epollfd, int fd, char *buf)
{
int nread = read(fd, buf, 256);
//当n等于0说明客户端断开连接
if(nread <= 0)
{
printf("Client is Closed.\n");
close(fd);
//删除fd的注册事件
delete_event(epollfd, fd, EPOLLIN);
}
printf("recv msg:>%s\n",buf);
//将事件改变为写
modify_event(epollfd, fd, EPOLLOUT);
}
//服务器向客户端写数据
void do_write(int epollfd, int fd, char *buf)
{
int nwrite = write(fd, buf, strlen(buf)+1);
//当nwrite为0时,表示服务器关闭
if(nwrite <= 0)
{
printf("server is closed.\n");
close(fd);
delete_event(epollfd, fd, EPOLLOUT);
}
else
//将事件改变为读
modify_event(epollfd, fd, EPOLLIN);
}
void handle_events(int epollfd, epoll_event *events, int num, int listenfd, char *buf)
{
int fd;
for(int i=0; i<num; ++i)
{
fd = events[i].data.fd;
//服务器的读事件时
if((fd==listenfd) && (events[i].events & EPOLLIN))
//新客户端建立连接
handle_accept(epollfd, listenfd);
else if(events[i].events & EPOLLIN)
//客户端的读事件,服务器从客户端读数据
do_read(epollfd, fd, buf);
else if(events[i].events & EPOLLOUT)
//客户端的写实现,服务器向客户端写数据
do_write(epollfd, fd, buf);
}
}
void do_epoll(int listenfd)
{
int epollfd;
epoll_event events[1024];
//创建文件描述符,唯一标识内核的事件表
epollfd = epoll_create(FDSIZE);
//增加listenfd的读事件
add_event(epollfd,listenfd, EPOLLIN);
int res;
char buf[256];
for(;;)
{
//返回就绪的文件描述符的个数
//并将就绪的事件从内核事件表复制到events中
res = epoll_wait(epollfd, events, 1024,-1);
if(res == -1)
{
perror("epoll_wait");
exit(1);
}
handle_events(epollfd, events, res, listenfd, buf);
}
close(epollfd);
}
int main()
{
int listenfd;
listenfd = sock_bind(IPADDR, PORT);
listen(listenfd, LISTENQ);
do_epoll(listenfd);
return 0;
}
客户端:
#include"../unp.h"
#include"utili.h"
void do_read(int epollfd,int fd,int sockfd,char*buf)
{
int nread;
nread = read(fd,buf,256);
if(nread == -1)
{
perror("read");
close(fd);
}else {
//从标准输入输出读
if(fd == STDIN_FILENO)
//读完增加可写事件
add_event(epollfd,sockfd,EPOLLOUT);
else{
//删除套接字的可读事件
delete_event(epollfd,sockfd,EPOLLIN);
//增加标准输入输出的可写事件
add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
}
}
}
void do_write(int epollfd,int fd,int sockfd,char*buf)
{
int nwrite;
nwrite = write(fd,buf,strlen(buf)+1);
if(nwrite == -1)
{
perror("write");
close(fd);
}
else
{
//从标准输入输出写
if(fd == STDOUT_FILENO)
//关闭套接字的写事件
delete_event(epollfd,fd,EPOLLOUT);
else
//将套接字的事件改变为可读
modify_event(epollfd,fd,EPOLLIN);
}
}
void handle_events(int epollfd,epoll_event *events,int num,int sockfd,char*buf)
{
int fd;
for(int i = 0; i < num; ++i)
{
fd = events[i].data.fd;
//可读事件
if(events[i].events & EPOLLIN)
do_read(epollfd,fd,sockfd,buf);
else if(events[i].events & EPOLLOUT)
//可写事件
do_write(epollfd,fd,sockfd,buf);
}
}
void handle_connection(int sockfd)
{
int epollfd;
epoll_event events[1024];
//创建内核注册表
epollfd = epoll_create(FDSIZE);
//增加标准输入输出的可读事件
add_event(epollfd,STDIN_FILENO,EPOLLIN);
int ret;
char buf[256];
for(;;)
{
ret = epoll_wait(epollfd,events,1024,-1);
handle_events(epollfd,events,ret,sockfd,buf);
}
close(epollfd);
}
int main(int argc,char* argv[])
{
int sockCli;
//创建套接字
sockCli = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(PORT);
addrSer.sin_addr.s_addr = inet_addr(IPADDR);
//连接服务器
connect(sockCli,(struct sockaddr* )&addrSer,sizeof(struct sockaddr));
handle_connection(sockCli);
return 0;
}
头文件utili.h:
#pragma once
#include"../unp.h"
void add_event(int epollfd, int fd, int event)
{
epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}
void modify_event(int epollfd, int fd, int event)
{
epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}
void delete_event(int epollfd, int fd, int event)
{
epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}
头文件unp.h:
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<poll.h>
#include<sys/epoll.h>
#include<sys/types.h>
#define IPADDR "127.0.0.1"
#define PORT 8787
#define MAXLINE 1024
#define LISTENQ 5
//select
#define SIZE 10
//poll
#define OPEN_SIZE 10
//epoll
#define FDSIZE 100
程序执行结果: