epoll是Linux多路服用IO接口select/poll的加强版,e对应的英文单词就是enhancement,中文翻译为增强,加强,提高,充实的意思。所以epoll模型会显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
所以,epoll是Linux大规模高并发网络程序的首选模型。
epoll使用一组函数来完成任务
创建一个epoll句柄,句柄的英文是handle,相通的意思是把手,把柄。
#include
int epoll_create(int size);
//返回值:若成功,返回一个非负的文件描述符,若出错,返回-1。
查看进程能够打开的最大数目的文件描述符
➜ ~ cat /proc/sys/fs/file-max
1215126
//该值与内存大小有关
修改最大文件描述符限制
➜ ~ sudo vim /etc/security/limits.conf
//重启生效
该函数用来操作epoll的内核事件表
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//返回值:若成功,返回0,若出错返回-1。
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events成员描述事件类型,将以下宏定义通过位或方式组合
data用于存储用户数据,是epoll_data_t结构类型,该结构定义如下:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
函数epoll_wait用来等待所监听文件描述符上有事件发生
#include
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//返回值:若成功,返回就绪的文件描述符个数,若出错,返回-1,时间超时返回0
#include
#include
#include "wrap.h"
#define MAX_EVENT_NUM 1024
#define BUFFER_SIZE 10
#define true 1
#define false 0
int setnonblocking(int fd)
{
int old_opt = fcntl(fd, F_GETFD);
int new_opt = old_opt | O_NONBLOCK;
fcntl(fd, F_SETFD, new_opt);
return old_opt;
}//将文件描述符设置为非阻塞的
void addfd(int epollfd, int fd, int enable_et)
{
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
if(enable_et){
event.events |= EPOLLET;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
// setnonblocking(fd);
}//将文件描述符fd的EPOLLIN注册到epollfd指示的epoll内核事件表中,enable_et表示是否对fd启用ET模式
void lt(struct epoll_event *events, int num, int epollfd, int listenfd)
{
char buf[BUFFER_SIZE];
for(int i = 0; i < num; i++){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in clientaddr;
socklen_t clilen = sizeof(clientaddr);
int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
addfd(epollfd, connfd, false);//对connfd使用默认的lt模式
}else if(events[i].events & EPOLLIN){//只要socket读缓存中还有未读的数据,这段代码就会触发
printf("event trigger once\n");
memset(buf, '\0', BUFFER_SIZE);
int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
if(ret <= 0){
Close(sockfd);
continue;
}
printf("get %d bytes of content:%s\n", ret, buf);
}else{
printf("something else happened\n");
}
}
}
void et(struct epoll_event *event, int num, int epollfd, int listenfd)
{
char buf[BUFFER_SIZE];
for(int i = 0; i < num; i++){
int sockfd = event[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in clientaddr;
int clilen = sizeof(clientaddr);
int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
addfd(epollfd, connfd, true);//多connfd开启ET模式
}else if(event[i].events & EPOLLIN){
printf("event trigger once\n");
while(1){//这段代码不会重复触发,所以要循环读取数据
memset(buf, '\0', BUFFER_SIZE);
int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
if(ret < 0){
if((errno == EAGAIN) || (errno == EWOULDBLOCK)){
printf("read later\n");
break;
}
Close(sockfd);
break;
}else if(ret == 0){
Close(sockfd);
}else{
printf("get %d bytes of content:%s\n", ret, buf);
}
}
}else{
printf("something else happened \n");
}
}
}
int start_ser(char *ipaddr, char *port)
{
int sock = Socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(port));
inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr);
Bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
Listen(sock, 128);
return sock;
}
int main(int argc, char *argv[])
{
int listenfd = start_ser(argv[1], argv[2]);
struct epoll_event events[MAX_EVENT_NUM];
int epollfd = epoll_create(5);
if(epollfd < 0){
perr_exit("epoll_create err");
}
addfd(epollfd, listenfd, true);
while(1){
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUM, -1);
if(ret < 0){
printf("epoll failure\n");
break;
}
lt(events, ret, epollfd, listenfd);//lt模式
//et(events, ret, epollfd, listenfd);//et模式
}
Close(listenfd);
return 0;
}
//warp.h文件是将socket,bind,listen等函数封装为第一个字母大写的头文件
#include "wrap.h"
int main(int argc, char *argv[])
{
int connfd;
struct sockaddr_in serveraddr;
char buf[1024];
connfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);
Connect(connfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
while(fgets(buf, 1024, stdin) != NULL){
Write(connfd, buf, strlen(buf));
}
Close(connfd);
return 0;
}
当发送超过缓冲区大小的数据量,LT会多次调用epoll_wait函数接受数据,则打印了多次“event level once”,而ET则是循环读取数据知道读完,打印了一次“event trigger once”。