epoll服务器
一、概述
epoll是Linux下多路复用IO接口select/poll的增强版本
epoll能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率
select使用轮询来处理,随着监听 fd 数目的增加而降低效率。而epoll只需要监听那些已经准备好的队列集合中的文件描述符,效率较高。
二、epoll API
头文件 #include
1.创建一个epoll句柄,参数size用来告诉内核监听的文件描述符个数,跟内存大小有关int epoll_create(int size)
参数
size 希望监听的文件描述符的个数(建议值),真实值跟内存相关
返回值
一个epoll句柄(返回一根结点,底层由红黑树构成)
2.控制某个epoll监控的文件描述符上的事件(注册、修改、删除)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
参数
epfd:为epoll_creat的句柄
op:表示动作,用3个宏来表示:
EPOLL_CTL_ADD(注册新的fd到epfd),
EPOLL_CTL_MOD(修改已经注册的fd的监听事件),
EPOLL_CTL_DEL(从epfd删除一个fd);
fd:需要操作的文件描述符
event:告诉内核需要监听的事件
其中 struct epoll_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;
events成员可以是如下值
EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
返回值
成功时返回 0,失败则返回 -1,并设置 errno
3.等待所监控文件描述符上有事件的产生,类似于select()调用int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
参数
events:用来从内核得到事件的集合,
maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
timeout:是超时时间
-1:阻塞
0:立即返回,非阻塞
大于0:指定微秒
返回值
成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
需要c/c++ Linux服务器高阶知识视频资料的朋友加群720209036获取
知识点有C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等。
三、epoll服务器模型#include
#include
#include
#include
#include
#include
#include
//#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
#define OPEN_MAX 1024
int main(int argc, char *argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;
int nready, efd, res;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
int client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
struct epoll_event tep, ep[OPEN_MAX];//接收 存放数据
//网络socket初始化
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
Listen(listenfd, 20);
for (i = 0; i < OPEN_MAX; i++)
client[i] = -1;
maxi = -1;//后面数据初始化赋值时 数据初始化为-1
//创建树
efd = epoll_create(OPEN_MAX);
if (efd == -1)
perr_exit("epoll_create");
//添加监听套接字
tep.events = EPOLLIN;
tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
if (res == -1)
perr_exit("epoll_ctl");
for ( ; ; )
{
//阻塞监听
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
if (nready == -1)
perr_exit("epoll_wait");
//如果有事件发生 开始数据处理
for (i = 0; i < nready; i++)
{
//是否是读事件
if (!(ep[i].events & EPOLLIN))
continue;
//若处理的事件和文件描述符相等 数据处理
if (ep[i].data.fd == listenfd)
{
//接收客户端
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
for (j = 0; j < OPEN_MAX; j++)
if (client[j] < 0)
{
//将通信套接字存放到client
client[j] = connfd;
break;
}
//是否到达最大值 保护判断
if (j == OPEN_MAX)
perr_exit("too many clients");
//更新client下标
if (j > maxi)
maxi = j;
//添加通信套接字到树上
tep.events = EPOLLIN;
tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
if (res == -1)
perr_exit("epoll_ctl");
}
else
{
//将connfd赋值给socket
sockfd = ep[i].data.fd;
//读取数据
n = Read(sockfd, buf, MAXLINE);
//无数据则删除该结点
if (n == 0)
{
//将Client中对应fd数据值恢复为-1
for (j = 0; j <= maxi; j++)
{
if (client[j] == sockfd)
{
client[j] = -1;
break;
}
}
//删除树结点
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if (res == -1)
perr_exit("epoll_ctl");
Close(sockfd);
printf("client[%d] closed connection\n", j);
}
else //有数据则写回数据
{
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Writen(sockfd, buf, n);
}
}
}
}
close(listenfd);
close(efd);
return 0;
}
补充
因为epoll可以处理并发连接数量比select的最大1024数量可以更多,一般需要修改最大文件描述符数量
修改文件描述符最大个数
编辑文件sudo vi /etc/security/limits.conf
写入以下配置(soft软限制,hard硬限制)* soft nofile 65536
* hard nofile 100000
之后重启linux再查看最大文件描述个数就修改成对应的65536了
查看最大文件描述符ulimit -n