目录
一. epoll简介
二. epoll相关系统的调用
1. epoll_create
2. epoll_ctl
3. epoll_wait
三. epoll工作方式
1. 水平触发模式(level-triggered,LT)
2. 边缘触发模式(edgetriggered,ET)
四.简易的epoll服务器代码编写
- 个人主页:努力学习的少年
- 版权: 本文由【努力学习的少年】原创、在CSDN首发、需要转载请联系博主
- 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦
epoll的功能一样跟select和poll一样,都是用来检测文件描述符中的事件是否就绪,当有事件就绪,可以通知给应用层,上层调用 read,recv,write,send 等类似接口就不会被阻塞。
我们之前学过select,poll应该知道,select 和 poll 有如下缺陷:
epoll通过两方面就很好的解决了select和epoll的缺陷
int epoll_create(int size);
调用epoll_create后,内核会创建一个epoll_create对象,对象中包括跟踪检测事件的红黑树,就绪队列,回调机制。
epoll_ctl 接口是用来 维护 epoll 对象中红黑树的节点,epoll_ctl可以在红黑树中添加,删除,修改节点。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *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本质是一个位图,它是用来表示事件的等待方式和事件的工作方式, 相对应的宏定义如下:
如果想要设置events多个条件,可以将用" | “表示,比如,既想要读事件又想要是ET的触发事件方式,则可以用 EPOLLIN | EPOLLET 表示。
epoll_data是一个联合体,他只能记录一个信息,他可以是指针,或者是一个文件描述符等等.如果是epoll服务器,epoll_data中一般记录的是socket文件描述符.
调用成功,返回0,调用失败返回-1,并设置errno错误码.
epoll_ctl本质是 以 fd-event 作为 key-value 映射关系插入到红黑树中,底层会根据红黑树节点的event中 events 判断是 需要检测读事件 还是检测写事件。
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
描述:调用epoll_wait能够获取就绪队列中已经就绪的事件。
epoll_wait本质是从就绪队列中已经就绪的节点event信息复制上来,上层可以通过event的信息判断是哪一个文件描述符事件就绪,events中判断是读事件就绪还是写事件就绪
epoll有两种触发模式,一种是水平触发(level-triggered,LT),
另一种是边缘触发(edgetriggered,ET),epoll默认的的工作方式是LT,如果想要设置ET工作方式,需要使用epoll_ctl进行设置。
使用水平触发模式,当socket缓冲区如果一直有数据,则就会一直触发回调函数将其socket的事件加入到就绪队列中,只有当socket缓冲区中没有数据,才不会触发回调函数.水平触发模式的socket,水平触发模式的socket可以不用一次性读取socket缓冲区中的数据,因为只要socket缓冲区有数据,则会一直触发回调函数,将socket的事件加入到就绪队列中,上层调用epoll_wait则就可以一直获得到该socket文件描述符.
总结:
使用边缘触发模式的效率相比使用水平触发模式的效率更高,因为 边缘触发模式 会逼迫上层一次性读完缓冲区,如果没有读取干净,则剩下的数据可能就不会读取到。每次检测socket触发回调机制,回调机制是会消耗cpu资源。
server.hpp文件
#pragma once
#include
#include
#include
#include
#include
#include
#define LOG_NUM 5
using std::cout;
using std::endl;
namespace sjp{
class server{
public:
server(){}
~server(){}
//创建套接字
static int Socket(){
int sockfd=socket(AF_INET,SOCK_STREAM,0);
return sockfd;
}
//绑定套接字接口
static bool Bind(int sockfd,unsigned short int port){
struct sockaddr_in s;
memset(&s,'\0',sizeof(s));
s.sin_family=AF_INET;
s.sin_port=htons(port);
s.sin_addr.s_addr=0;
if(bind(sockfd,(struct sockaddr*)&s,sizeof(s))<0){
cout<<"bind error"<
epoll_server.hpp文件
#pragma once
#include"server.hpp"
#define NUM 1024
#define WAIT_NUM 32;
#include
namespace ep_server{
class EpollServer{
private:
int port;//端口号
int listen_sock;//监听套接字
int epfd;
public:
EpollServer(int _port):port(_port){}
void InitServer(){
listen_sock=sjp::server::Socket();
sjp::server::Bind(listen_sock,port);
sjp::server::Listen(listen_sock);
epfd=epoll_create(NUM);
}
void Run(){
Addevent(listen_sock,EPOLLIN);
while(1){
struct epoll_event ep[32];//保存就绪事件
int sz=epoll_wait(epfd,ep,32,1000);//sz是获取就绪事件的个数
if(sz>0){
for(int i=0;i0){
Addevent(fd,EPOLLIN);//将新的socket添加到红黑树中
}
}
else{
char str[1024];
size_t sz=recv(ep[i].data.fd,(void*)str,1024,MSG_DONTWAIT);
if(sz>0){
str[sz]='\0';
cout<
epoll_server.cc
#include"epoll_server.hpp"
#include
void Usage(){
cout<<"Usage Way: epollserver port"<InitServer();
es->Run();
}