epoll模型可以说是select模型和poll模型的升级版,但epoll要求在linux内核版本2.6以上。
相对于,select和poll来说,epoll更加灵活,没有描述符限制。相比于select和poll,epoll不会因为监听的描述符数目变多而导致轮询过多(耗时太多),不会因为fd的数目增大而降低响应效率。
另外,对于select的最大描述符,linux会有限制,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE 1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
epoll使用一个描述符去管理多个套接字(多个描述符),其实这个管理其他套接字的描述符就相当于一个handle,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核结构之间的数据拷贝只需一次。
使用epoll模型需要包含头文件
#include <sys/epoll.h>
epoll提供了3个函数
(1) int epoll_create(int size);
epoll_create创建了一个epoll句柄(handle,或称为实例),这个实例本质上也是一个文件描述符。参数size是通知内核要监听的描述符数量,但自从linux 内核2.6.8后,这个参数是忽略的,只有设置为大于0就可以。
参数size的监听数量与select的第一个参数有所不同,select的第一个参数要求是监听的描述符中最大值+1,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽(其实就是必须销毁句柄的意思)。
当epoll_create返回-1时,表示epoll_create失败,同时会去设置errno,返回非负值,也就是文件描述符,表示epoll_create成功。
(2) int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数epoll_ctl的主要功能是添加描述符及为相关描述符的注册事件。
它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数 epfd就是epoll_create创建出的epoll句柄
第二个参数op属于操作宏,有三个类型:
a. EPOLL_CTL_ADD
注册新的fd(第三个参数)到epfd中;
b. EPOLL_CTL_MOD
改变fd的已经注册过的事件。(给fd设置一个新的事件)
c. EPOLL_CTL_DEL
从epoll句柄管理的描述符集中删除fd
第三个参数fd就是操作的文件描述符。
第四个参数event是struct epoll_event *结构体指针。
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 */
};
struct epoll_event 结构体成员events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level
Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
epoll_ctl返回值:
成功返回值为0,失败返回值为-1.
(3)int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_wait灵活等待事件的到来,类似于select()调用。参数events用来从内核得到事件的集合(传入还是传出的),maxevents告知内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(单位毫秒,0会立即返回,-1永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
然后详细解释ET, LT:
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)
使用epoll
服务器端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/epoll.h>
extern int errno;
#define MaxConnectNum (5)
#define EPOLL_SIZE (1000)
#define EPOLL_EVENT_SIZE (100)
int main()
{
int domain = AF_INET;
int type = SOCK_STREAM;
int protocol = 0;
int ret = -1;
int nListenFd = -1;
int nNewClientFd = -1;
short int port = 2000;
struct sockaddr_in addr_in;
int backlog = 128; // 默认是128
int len = 0;
char chBuffer[1024] = {0};
int flags = 0;
int nMaxFd = -1;
int i = 0;
static int s_nCountClient = 0;
int epoll_handle_fd = -1;
struct epoll_event stuEventTmp;
struct epoll_event stuEventArray[EPOLL_EVENT_SIZE];
nListenFd = socket( domain, type, protocol);
if(nListenFd < 0)
{
printf("\n socket failed ! errno[%d] err[%s]\n", errno, strerror(errno));
return -1;
}
memset(&addr_in, 0, sizeof(struct sockaddr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(port);//htons的返回值是16位的网络字节序整型数 htons尾的字母s代表short
addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(nListenFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
if(ret < 0)
{
printf("\n bind failed ! errno[%d] err[%s]\n", errno, strerror(errno));
close(nListenFd); //避免资源泄漏
return -1;
}
ret = listen(nListenFd, backlog);
if(ret < 0)
{
printf("\n listen failed ! errno[%d] err[%s]\n", errno, strerror(errno));
close(nListenFd); //避免资源泄漏
return -1;
}
epoll_handle_fd = epoll_create(EPOLL_SIZE);
if(-1 == epoll_handle_fd)
{
printf("\n epoll_create failed ! errno[%d] err[%s]\n", errno, strerror(errno));
close(nListenFd); //避免资源泄漏
return -1;
}
//添加listenfd
// int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
memset(&stuEventTmp, 0, sizeof(struct epoll_event));
stuEventTmp.data.fd = nListenFd;
stuEventTmp.events = EPOLLIN ;
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_ADD, nListenFd,&stuEventTmp );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
close(nListenFd); //避免资源泄漏
return -1;
}
while(1)
{
int num = 0;
int timeout = 5000;
num = epoll_wait(epoll_handle_fd, stuEventArray, EPOLL_EVENT_SIZE,timeout);//int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
if(num < 0)
{
break;
}
else if(num == 0)
{
printf("\n epoll time out\n");
continue;
}
else
{
for(i=0; i < num; i++)
{
if(stuEventArray[i].data.fd == nListenFd)
{
nNewClientFd = accept(nListenFd, ( struct sockaddr *)NULL, NULL); //阻塞模式
if(nNewClientFd < 0)
{
printf("\n accept failed ! errno[%d] err[%s]\n", errno, strerror(errno));
//close(nListenFd); //避免资源泄漏
break;
}
//添加
memset(&stuEventTmp, 0, sizeof(struct epoll_event));
stuEventTmp.data.fd = nNewClientFd;
stuEventTmp.events = EPOLLIN ;
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_ADD, nNewClientFd,&stuEventTmp );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
}
else if( stuEventArray[i].data.fd != -1)
{
if((stuEventArray[i].events & EPOLLIN))
{
//READ DATA
len = recv(stuEventArray[i].data.fd , chBuffer, sizeof(chBuffer) , flags);//flags为0,阻塞模式
if(len <= 0)
{
printf("\n recv failed ! errno[%d] err[%s] len[%d]\n", errno, strerror(errno),len);
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_DEL, stuEventArray[i].data.fd,&stuEventArray[i] );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
close(stuEventArray[i].data.fd);
continue;
}
printf("\n recv [%s] len[%d]\n", chBuffer,len);
stuEventArray[i].events = EPOLLOUT ;
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_MOD, stuEventArray[i].data.fd,&stuEventArray[i] );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
continue;
}
#if 1
if(stuEventArray[i].events & EPOLLOUT )
{
//SEND DATA
len = send(stuEventArray[i].data.fd , chBuffer, sizeof(chBuffer) , flags);//flags为0,阻塞模式
if(len <= 0)
{
printf("\n send failed ! errno[%d] err[%s] len[%d]\n", errno, strerror(errno),len);
close(stuEventArray[i].data.fd);
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_DEL, stuEventArray[i].data.fd,&stuEventArray[i] );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
continue;
}
printf("\n send [%s] len[%d]\n", chBuffer,len);
stuEventArray[i].events = EPOLLIN ;
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_MOD, stuEventArray[i].data.fd,&stuEventArray[i] );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
continue;
}
#endif
}
}
if(i != num )
{
printf("\n if(i != num ) \n");
break;
}
}
}
return 0;
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
extern int errno;
int main()
{
int domain = AF_INET;//AF_INET
int type = SOCK_STREAM;
int protocol = 0;
int ret = -1;
int nClientFd = -1;
short int port = 2000;
struct sockaddr_in addr_in;
int len = 0;
char chBuffer[1024] = {0};
int flags = 0;
char * pchServerIP = "192.168.1.211";
nClientFd = socket( domain, type, protocol);
if(nClientFd < 0)
{
printf("\n socket failed ! errno[%d] err[%s]\n", errno, strerror(errno));
return -1;
}
memset(&addr_in, 0, sizeof(struct sockaddr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(port);//htons的返回值是16位的网络字节序整型数 htons尾的字母s代表short
//addr_in.sin_addr.s_addr = htonl(inet_addr(pchServerIP)); //错误的做法
addr_in.sin_addr.s_addr = inet_addr(pchServerIP);
ret = connect(nClientFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
if(ret < 0)
{
printf("\n connect failed ! errno[%d] err[%s]\n", errno, strerror(errno));
close(nClientFd); //避免资源泄漏
return -1;
}
printf("\n connect success ! \n");
for(;;)
{
len = send(nClientFd, "2", sizeof("2"), flags);
sleep(2);
}
close(nClientFd);
return 0;
}