seo百度站长工具查询,iis6建设网站,新开传奇网站195合击,安徽公共资源交易中心为什么要做高性能网络IO。主要是解决c10#xff0c;c10M问题 最开始的时候我们走的内核协议栈#xff0c;走内核协议栈其实性能比较低#xff0c;因为我们之前介绍的时候需要拷贝两次
但是我们采用用户态协议栈可以少拷贝一次#xff0c;可以大大提高效率#xff0c; 步骤…为什么要做高性能网络IO。主要是解决c10c10M问题 最开始的时候我们走的内核协议栈走内核协议栈其实性能比较低因为我们之前介绍的时候需要拷贝两次
但是我们采用用户态协议栈可以少拷贝一次可以大大提高效率 步骤
1客户端请求数据先经过网卡服务器需要从网卡copy数据到内核协议栈tcp/bsd。
2再从内核协议栈copy数据到应用程序。
由此可见客户端与应用程序之间的数据交互多了两次数据拷贝的操作在大量数据并发的情况下必将会严重影响性能。
优化思路可以跳过内核协议栈去除拷贝操作数据直接从网卡到应用程序这种方式称为零拷贝。 但是我们这个在做完用户态协议栈在配合reactor的时候会出现一个问题是什么问题呢
问题是我们的epoll并不会通知这个事件
那么我们是怎么看待这个问题的呢我们如果不了解epoll的原理和底层的话我们一下也不知道为什么epoll不会通知其实是因为epoll的通知是由内核通知的但是我们旁路之后不走内核协议栈那么内核协议栈就不会通知数据了 图例(我们是在内核里创建了红黑树和fd的一些结构体信息然后提供系统调用给用户)
协议栈解析出有数据来通知到epoll中。应用程序操作epoll 所以我们在走用户态协议栈的时候就不能用系统自带的epoll了需要自己再用户态实现一个 epoll进行管理 我们怎么设计epoll呢我们采用红黑树结构是最好的排除掉哈希表优先队列链表
就是红黑树最好了为什么因为红黑树这个数据结构更适合增删改查效率也高并且有
带有二叉搜索树的性质所以很好用
需要查找性能高的数据结构可选的数据结构有
hashfd 数量不确定创建 hash 消耗大量的内存。若 fd 数量较少时内存浪费多性能低b/b 树查找性能低于红黑树降低树高用于磁盘 iorbtree查找性能高效率稳定这里选用红黑树 还有一点就是epoll 监听的是系统 fd。而在自定义用户态协议栈的过程中我们定义的 fd 只是个 int 值并不指向内核打开文件表中对应的 i-node 结点
epoll 通过 fd 检测协议栈中的 tcb 有无事件发生并对这些 fd 进行管理。 自定义epoll 的主要结构体有 epitem存储每个 io 对应的事件每个注册到 epoll 池的 fd 对应1个 epitem
// 自定义的 epitem
struct epitem {RB_ENTRY(epitem) rbn; // 红黑树的结点LIST_ENTRY(epitem) rdlink; // 就绪队列双向链表结点int rdy; // 是否在就绪队列中int sockfd; // 事件对应的sockfdstruct epoll_event event; // 注册事件的类型
};eventpoll用于管理1个 epoll 对象
// 自定义的 eventpoll
struct eventpoll {int fd; // epfdep_rb_tree rbr; // 红黑树的根结点int rbcnt; LIST_HEAD( ,epitem) rdlist; // 就绪队列头结点int rdnum; int waiting; // epoll_wait判断是否正在等待pthread_mutex_t mtx; //rbtree updatepthread_spinlock_t lock; //rdlist updatepthread_cond_t cond; //block for event用于epoll_wait的超时等待pthread_mutex_t cdmtx; //mutex for cond
};红黑树和双向链表共用结点 epitem。
双向链表采用的是就绪队列在处理事件的时候可以按先来先服务策略进行处理时间 2、epoll 锁机制
考虑两个公共资源红黑树和就绪队列。
红黑树mutex互斥锁就绪队列spinlock采用自旋锁避免 SMP 体系下多核竞争。 我们的红黑树的删除和修改和插入都是采用互斥锁的因为不用锁的的话会发生线程安全问题比如我们将epoll交给多个线程管理那么当事件就绪的时候就会有惊群效应如果此时
不加锁的话那么多个线程会同时去处理这个事件那么就会出现线程安全问题 3、epoll 用户态接口
epoll 为用户态提供的接口有epoll_create, epoll_ctl, eoll_wait 3.1、epoll_create 的实现
功能 创建 eventpoll 结构体
int epoll_create(int size) {if (size 0) return -1;// 从位图中获取新的fdfd从3开始依次递增 int epfd get_fd_frombitmap();struct eventpoll *ep (struct eventpoll*)rte_calloc(eventpoll,1, sizeof(struct eventpoll), 0);if (!ep) {// 创建失败将fd从位图中删除set_fd_frombitmap(epfd);return -1;}// 初始化红黑树和就绪队列ep-rbcnt 0;RB_INIT(ep-rbr);LIST_INIT(ep-rdlist);if (pthread_mutex_init(ep-mtx, NULL)) {rte_free(ep);set_fd_frombitmap(epfd);return -2;}if (pthread_mutex_init(ep-cdmtx, NULL)) {pthread_mutex_destroy(ep-mtx);rte_free(ep);set_fd_frombitmap(epfd);return -2;}if (pthread_cond_init(ep-cond, NULL)) {pthread_mutex_destroy(ep-cdmtx);pthread_mutex_destroy(ep-mtx);rte_free(ep);set_fd_frombitmap(epfd);return -2;}if (pthread_spin_init(ep-lock, PTHREAD_PROCESS_SHARED)) {pthread_cond_destroy(ep-cond);pthread_mutex_destroy(ep-cdmtx);pthread_mutex_destroy(ep-mtx);rte_free(ep);set_fd_frombitmap(epfd);return -2;}return epfd;
}3.2、epoll_ctl 的实现
功能对红黑树进行增添修改、删除。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) {// 通过fd查找到协议栈中对应的tcb连接返回 eventpoll 对象fd - hoststruct eventpoll *ep (struct eventpoll *)get_hostinfo_fromfd(epfd);// 若ep对象为空或没有要设置的事件del除外if (!ep || (!event op ! EPOLL_CTL_DEL)) {errno -EINVAL;return -1;}///1、ADD 操作if (op EPOLL_CTL_ADD) {pthread_mutex_lock(ep-mtx);struct epitem tmp;tmp.sockfd fd;// 在红黑树查找该结点 struct epitem *epi RB_FIND(_epoll_rb_socket, ep-rbr, tmp);// 若红黑树已经存在该结点返回if (epi) {pthread_mutex_unlock(ep-mtx);return -1;}// 不存在则创建 epitem 结点并为其添加sockfd和事件epi (struct epitem*)rte_calloc(epitem,1, sizeof(struct epitem), 0);if (!epi) {pthread_mutex_unlock(ep-mtx);errno -ENOMEM;return -1;} epi-sockfd fd;memcpy(epi-event, event, sizeof(struct epoll_event));// 插入到红黑树中epi RB_INSERT(_epoll_rb_socket, ep-rbr, epi);assert(epi NULL);// 红黑树结点数量增加ep-rbcnt ;pthread_mutex_unlock(ep-mtx);} // 2、DEL 操作else if (op EPOLL_CTL_DEL) {pthread_mutex_lock(ep-mtx);struct epitem tmp;tmp.sockfd fd;struct epitem *epi RB_FIND(_epoll_rb_socket, ep-rbr, tmp);// 若红黑树中不存在该结点直接返回if (!epi) {pthread_mutex_unlock(ep-mtx);return -1;}// 存在该结点则从红黑树中删除epi RB_REMOVE(_epoll_rb_socket, ep-rbr, epi);if (!epi) {pthread_mutex_unlock(ep-mtx);return -1;}// 红黑树结点数量减少ep-rbcnt --;// 释放结点空间rte_free(epi);pthread_mutex_unlock(ep-mtx);} // 3、MOD 操作else if (op EPOLL_CTL_MOD) {struct epitem tmp;tmp.sockfd fd;struct epitem *epi RB_FIND(_epoll_rb_socket, ep-rbr, tmp);// 该结点存在则修改if (epi) {epi-event.events event-events;epi-event.events | EPOLLERR | EPOLLHUP;} // 不存在返回-1else {errno -ENOENT;return -1;}} // 4、非法操作else {assert(0);}return 0;
}3.3、epoll_wait 的实现 功能等待 fd 就绪监控就绪队列若有数据从内核拷贝数据到用户空间若没有数据阻塞。
等待的实现方法
等待规定的时间条件变量 pthread_cond_timedwait 一直等待阻塞条件变量 pthread_cond_wait
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) {// 通过fd查找到协议栈中对应的tcb连接返回 eventpoll 对象fd - hoststruct eventpoll *ep (struct eventpoll *)get_hostinfo_fromfd(epfd);// 若ep对象为空或没有要设置的事件del除外if (!ep || (!event op ! EPOLL_CTL_DEL)) {errno -EINVAL;return -1;}///1、ADD 操作if (op EPOLL_CTL_ADD) {pthread_mutex_lock(ep-mtx);struct epitem tmp;tmp.sockfd fd;// 在红黑树查找该结点 struct epitem *epi RB_FIND(_epoll_rb_socket, ep-rbr, tmp);// 若红黑树已经存在该结点返回if (epi) {pthread_mutex_unlock(ep-mtx);return -1;}// 不存在则创建 epitem 结点并为其添加sockfd和事件epi (struct epitem*)rte_calloc(epitem,1, sizeof(struct epitem), 0);if (!epi) {pthread_mutex_unlock(ep-mtx);errno -ENOMEM;return -1;} epi-sockfd fd;memcpy(epi-event, event, sizeof(struct epoll_event));// 插入到红黑树中epi RB_INSERT(_epoll_rb_socket, ep-rbr, epi);assert(epi NULL);// 红黑树结点数量增加ep-rbcnt ;pthread_mutex_unlock(ep-mtx);} // 2、DEL 操作else if (op EPOLL_CTL_DEL) {pthread_mutex_lock(ep-mtx);struct epitem tmp;tmp.sockfd fd;struct epitem *epi RB_FIND(_epoll_rb_socket, ep-rbr, tmp);// 若红黑树中不存在该结点直接返回if (!epi) {pthread_mutex_unlock(ep-mtx);return -1;}// 存在该结点则从红黑树中删除epi RB_REMOVE(_epoll_rb_socket, ep-rbr, epi);if (!epi) {pthread_mutex_unlock(ep-mtx);return -1;}// 红黑树结点数量减少ep-rbcnt --;// 释放结点空间rte_free(epi);pthread_mutex_unlock(ep-mtx);} // 3、MOD 操作else if (op EPOLL_CTL_MOD) {struct epitem tmp;tmp.sockfd fd;struct epitem *epi RB_FIND(_epoll_rb_socket, ep-rbr, tmp);// 该结点存在则修改if (epi) {epi-event.events event-events;epi-event.events | EPOLLERR | EPOLLHUP;} // 不存在返回-1else {errno -ENOENT;return -1;}} // 4、非法操作else {assert(0);}return 0;
}4、epoll 回调
4.1、epoll 回调函数的实现 当内核 io 准备就绪的时候执行 epoll 回调函数将 epitem 添加到 rdlist 中唤醒 epoll_wait。当 epoll_wait 被激活重新运行的时候将 rdlist 的 epitem 逐一拷贝到 events 中同时删除 rdlist 中对应的结点。换句话说 epoll_callback 是生产者放入结点唤醒 epoll_waitepoll_wait 是消费者消费结点。
// 从协议栈回调到epoll把fd和对应的事件拷贝到应用程序
static int nepoll_event_callback(struct eventpoll *ep, int sockid, uint32_t event) {struct epitem tmp;tmp.sockfd sockid;// 在红黑树中查找 epitemstruct epitem *epi RB_FIND(_epoll_rb_socket, ep-rbr, tmp);if (!epi) {return -1;}// 已经在就绪队列中只添加事件if (epi-rdy) {epi-event.events | event;return 1;} // 不在就绪队列则将结点加入到就绪队列pthread_spin_lock(ep-lock);epi-rdy 1;LIST_INSERT_HEAD(ep-rdlist, epi, rdlink);ep-rdnum ;pthread_spin_unlock(ep-lock);pthread_mutex_lock(ep-cdmtx);// 就绪队列中增加结点唤醒epoll_waitpthread_cond_signal(ep-cond);pthread_mutex_unlock(ep-cdmtx);return 0;}4.2、epoll 回调的时机 触发 epoll 回调4个时机需要在这些地方添加 epoll 回调函数使得 epoll 可以正常接收数据。
三次握手中在 syn-rcvd 状态对端返回 ack 后tcb 结点放入到全连接队列将对应的 sockfd 的置为 EPOLLIN 状态等待 accept 取出触发 epoll 回调。
if (stream-status TCP_SYN_RCVD) {// 进入到 ESTABLISHED 状态stream-status TCP_STATUS_ESTABLISHED;// 设置 epoll 回调函数等待 accept
}在 established 状态收到数据后将 sockfd 置为 EPOLLIN 状态等待读取数据触发epoll 回调
if (tcphdr-tcp_flags TCP_PSH_FLAG) {// 建立连接后push 接收数据设置 epoll 回调函数
} 在 established 状态收到 fin 时进入到 close_wait 状态。将 sockfd 的 event 置为 EPOLLIN读取断开信息触发 epoll 回调
if (tcphdr-tcp_flags TCP_FIN_FLAG) {// 收到 fin进入到 CLOSE_WAIT 状态stream-status TCP_STATUS_CLOSE_WAIT; // 设置 epoll 回调函数读取断开信息
}检测 socket 的 send 状态如果对端 cwnd0 可以发送数据将 sockfd 置为 EPOLLOUT等待发送数据
5、epoll 事件通知机制 水平触发(LT)有事件则一直触发边缘触发(ET)只触发一次关注的是 io 状态的变化。
实现的关键是内核 io 就绪时epoll 回调函数的执行次数。
LT检测 recvbuffer 有数据则调用 epoll 回调函数 ET从协议栈中检测到recvbuffer中接收数据就调用 epoll 回调函数
我们后面还可以用io_uring来处理先不介绍了