epoll
epoll 优点:
- 接口使用方便,不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
- 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中
- 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1)
- 没有数量限制: 文件描述符数目无上限
select、poll、epoll对比:
api
- epoll_create: 创建epoll
int epoll_create(int size);
- epoll_ctl : 注册监听的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
第一个参数是epoll_create()的返回值(epoll的句柄).
第二个参数表示动作,用三个宏来表示.
第三个参数是需要监听的fd.
第四个参数是告诉内核需要监听什么事
op:
EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd
events:
EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
EPOLLOUT : 表示对应的文件描述符可以写;
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
- epoll_wait : 将就绪队列从内核态到用户态
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
events: 收就绪IO的袋子
maxevents: events的大小
timeout: 超时事件,ms
返回值:函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败
流程:
在内核中创建epoll,accept创建io,判断是否加入到内核中,每一次将就绪的io切换到用户态
底层实现:
epoll_create: 创建红黑树,创建回调机制,创建就绪队列
epoll_ctrl: 添加、删除、修改红黑树节点,建立对应的文件描述符的回调函数
红黑树的每个节点包含:fd文件描述符,event:对应的事件
在os层,使用驱动层的功能,完成某些回调功能,那些文件描述符已经就绪,则放入就绪队列中
epoll_wait: 负责等待就绪从就绪队列中取出节点
触发方式
水平触发(LT) :满足IO复用条件即触发
边沿触发 (ET) : 新的IO就绪事件到达即触发
如客户端一次发送数据100个字节数据到服务端,服务端一次读50个字节,
服务器设置为水平触发,则recv执行2次,
服务器设置为边缘触发,则recv执行1次
使用场景: 对于发送的数据包较大,则设置为边缘触发触发,循环读取数据;数据包较小,则设置为水平触发
code(service)
service-epoll:
运行服务端程序,将阻塞在epoll_wait,当客户端连接时,服务端接收客户端buffer,并发送buffer给客户端
int sockfd= socket(AF_iNET,SOCK_STREAM,0);
if(sockfd == -1) {
return -1;
}
struct socketaddr_in servaddr;
memset(&servaddr,0,sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
if(-1 = bind(sockfd,(struct sockaddr*)&servaddr),sizeof(servaddr)){
return -2;
}
//nonblock
int flag = fcntl(sockfd,F_GETFL,0);
flag |= O_NONBLOCK;
fcntl(sockfd,F_SETFL,flag);
listen(sockfd,10);
//epoll
int epfd = epoll_create(1);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd; // 8个字节
epoll_ctl(epfd,EPOLL_CEL_ADD,sockfd,&ev);
struct epoll_event events[1024] = {0}; // events快递员装快递的袋子
struct sockaddr_in clientaddr;
socklen_t len = sizeof(client);
while(1) { // loop
int nready = epoll_wait(epfd,events,1024,-1);// 取快递 最后一个参数,-1 一直等待,0,不等待,1,等待一段时间
if(nready < 0 ) continue;
int i = 0;
// set 链接额的客户端 ready 盒子
for(i = 0; i< nready;i++) {
int connfd = events[i].data.fd;
if(sockfd == connfd) {
int clientfd = accept(sockfd,(struct sockaddr *)clientaddr,&len);
if(clientfd <= 0 ) {
continue;
}
printf("clientfd = %d",clientfd);
// EPOLLET 边沿触发
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clientfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
}
else if (events[i].events & EPOLLIN) {
char buffer[BUFFER_LENGTH] = {0};
int n = recv(connfd,buffer,BUFFER_LENGTH);
if(n > 0) {
// 服务器先接受客户端的buffer,再将buffer发送给客户端
printf("recv : %s\n",buffer);
send(connfd,buffer,n,0);
} else if (n == 0) {
printf("close\n");
// 用户搬走
epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
// 如不移除,connfd的值将会一直存在
close(connfd);
}
}
}
}
}
参考文章:
https://blog.youkuaiyun.com/weixin_45599288/article/details/123893844