epoll实现多路IO

  1. 源码地址:https://github.com/whuwzp/linuxc/tree/master/epoll
  2. 源码说明:

1. 概要

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout); typedef union epoll_data { //联合结构体,占用同一个内存空间,同一时间只有一个有效
void *ptr; //指针,可以指向任何对象,在后面会介绍指向结构体
int fd; //文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t; struct epoll_event {
uint32_t events; /* Epoll events */ //监听的事件,如EPOLLIN
epoll_data_t data; /* User data variable */ // 其他数据部分,这个是我们可以设定的,因为epoll_wait返回时会把这个结构体返回到events中,我们就可获取有信号的属性了
};

1.1 epoll_create

epfd = epoll_create(FD_SETSIZE); //返回文件描述符epfd,是红黑树的根节点

1.2 epoll_ctl

//增加节点
evt.data.fd = fd_client;//这里的联合体选择了int fd
evt.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd_client, &evt);
//删除节点
epoll_ctl(epfd, EPOLL_CTL_DEL, evts[i].data.fd, nullptr);

增加节点: 是把fd_client文件描述符添加到epfd的红黑树中,同时附带自己的属性,即epoll_event类型的结构体(这个结构体在epoll_wait后会复制到events数组中,这样返回时就可以获取有信号的fd的属性了)

删除节点: 从树中移除

1.3 epoll_wait

nselect = epoll_wait(epfd, evts, FD_SETSIZE, -1);

epfd: 输入,待监听的红黑树

evts: 输出,数组中的元素类型是epoll_event,当fd==n的文件描述符有信号时,会把fd的属性(详见epoll_ctl, 也是epoll_event类型)拷贝到evts中,这样后续我们就可以访问evts[i].data.xxxevts[i].events来获取我们之前设定的数据

1.4 原理

  1. 树中每个节点是fd,每个fd映射着一个epoll_event类型的结构体,像属性一样(epoll_ctl添加节点时自定义的)
  2. epoll_wait监听各个节点,有信号的话,就把它的属性(epoll_event)拷贝到evts中,返回后,就可以通过evts[i].data.xxx得到映射的数据
  3. 如果联合体选择fd, 即evts[i].data.fd,我们就相当于直接获取了fd,可以直接read,write.
  4. 后面的epoll_libevent我们会用ptr指向自定义的结构体

1.5 优势劣势

一定先epoll实现,epoll是poll的改进版,有以下不同:

  1. 结果返回(判断是否有信号)

    • poll:需要轮询数组中的每个元素,看看是不是有信号,用fds[0].revents & POLLIN
    • epoll:一个数组evts专门存放有信号的节点的属性

2. 核心代码

#include "include/wrap.h"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <unistd.h>
#include <wait.h>
#define LOCALIP "127.0.0.1"
#define PORT 6666 void handler(char *in, char *out) {
for (int i = 0; i < (int)strlen(out) + 1; ++i) {
out[i] = toupper(in[i]);
}
} int workthread(const int &fd_client) {
char recvbuf[2048] = {0};
char sendbuf[2048] = {0};
int ret = 0; ret = (int)Read(fd_client, recvbuf, 2048);
if (ret <= 0) {
printf("ret==0\n");
return ret;
} handler(recvbuf, sendbuf); ret = (int)Write(fd_client, sendbuf, strlen(sendbuf) + 1);
return ret;
} void startsock(int &fd, struct sockaddr_in &addr, const char *ip,
const int port) {
fd = Socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_port = htons(port);
}
int main() {
int fd_server = 0;
int fd_client = 0;
int ret = 0;
struct sockaddr_in sock_client;
struct sockaddr_in sock_server;
socklen_t client_len = (socklen_t)sizeof(sock_client);
int opt = 0;
int epfd = 0;
int nselect = 0;
int i = 0;
struct epoll_event evts[FD_SETSIZE];
struct epoll_event evt;
startsock(fd_server, sock_server, LOCALIP, PORT);
opt = 1;
Setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, &opt,
(socklen_t)sizeof(opt));
Bind(fd_server, (struct sockaddr *)&sock_server, sizeof(sock_server));
Listen(fd_server, 5);
epfd = epoll_create(FD_SETSIZE);
if (epfd == -1) { perror_exit("epoll create failed"); } evt.events = EPOLLIN;
evt.data.fd = fd_server;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd_server, &evt); while (true) {
printf("epolling...\n");
nselect = epoll_wait(epfd, evts, FD_SETSIZE, -1);
printf("get %d select\n", nselect);
for (i = 0; i < nselect; ++i) {
if (!(evts[i].events && EPOLLIN)) continue;
if (evts[i].data.fd == fd_server) {//直接拿到fd_server
fd_client = Accept(fd_server, (struct sockaddr *)&sock_client,
&client_len);
printf("accept: %s: %d\n", inet_ntoa(sock_client.sin_addr),
ntohs(sock_client.sin_port));
evt.data.fd = fd_client;
evt.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd_client, &evt);
} else {
ret = workthread(evts[i].data.fd);//通过data.fd获取fd
if (ret <= 0) {
Close(evts[i].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, evts[i].data.fd, nullptr);
}
}
}
}
Close(fd_server);
}

3. 参考网址

  1. https://www.bilibili.com/video/av53016117
  2. EPOLLOUT的用处:https://www.zhihu.com/question/22840801 https://github.com/yedf/handy

    write: 不停地写数据,因为EPOLLOUT是只要缓冲区未满,就会有信号

最新文章

  1. Interface小例子
  2. 架构Android App总结
  3. jquery中对动态生成的标签响应click事件(二)…与ajax交互使用
  4. java集合类(二)
  5. JAVA中单例模式的几种实现方式
  6. leetcode算法
  7. Canvas 获取颜色值
  8. [转]从两道经典试题谈C/C++中联合体(union)的使用
  9. Android(java)学习笔记105:Map集合的遍历之键值对对象找键和值
  10. poj2594 (最小路径覆盖 + floyd)
  11. 【HDOJ】1160 FatMouse&#39;s Speed
  12. head first 设计模式读书笔记 之 策略模式
  13. Python基础篇-day5
  14. JavaIO 总结
  15. canvas百分比加载动画
  16. Django rest framework源码分析(1)----认证
  17. 19.C# 泛型
  18. centos7如何安装gcc5.4
  19. Leetcode 137 Single Number II 仅出现一次的数字
  20. [Spark]What&#39;s the difference between spark.sql.shuffle.partitions and spark.default.parallelism?

热门文章

  1. JMeter脚本拷贝自动化
  2. SOFARPC模式下的Consul注册中心
  3. 物联网时代-新基建-ThingsBoard调试环境搭建
  4. Koadic的安装和使用
  5. PHP中$$的应用
  6. 数学-概率-New 21 Game
  7. MySQL MyISAM和Innodb表生成序列
  8. [JVM教程与调优] 了解JVM 堆内存溢出以及非堆内存溢出
  9. React Hooks 实现react-redux
  10. 4.用IntelliJ IDEA 创建Maven Web