epoll监听文件_Linux高并发服务器——epoll服务器

epoll服务器

一、概述

epoll是Linux下多路复用IO接口select/poll的增强版本

epoll能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率

select使用轮询来处理,随着监听 fd 数目的增加而降低效率。而epoll只需要监听那些已经准备好的队列集合中的文件描述符,效率较高。

二、epoll API

头文件 #include <sys/epoll.h>

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等等。

c487d19457def18000322ef6bf469fb4.png

81609b5216cdbdb0ab797354795f3379.png

三、epoll服务器模型

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
//#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 %dn",
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 connectionn", 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

e4289041634297bd3a4e8e17680cefc3.png

之后重启linux再查看最大文件描述个数就修改成对应的65536了

查看最大文件描述符

ulimit -n

ee1df96f9fc2435c2d7723cb69f044d7.png
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页