1 epoll编程问题
对于数据传输send/recv这些接口的而言;如何判断send数据,对方有没有接收成功?
采用send返回值无意义。,send发送数据只是将数据拷贝到协议栈,而数据的发送是协议栈自动处理的(何时发送也是由自己组装的).
2 粘包分包问题
产生原因:应用程序多次调用send后,只是将数据从用户空间拷贝到内核协议栈。而对于协议栈很容将两次的send包一起发送。
解决方案:a): tcp应用层协议添加长度域
b)每个包加上分割符
c)定长包(比较low,不推荐使用)
3 多个线程,并发的给fd发送数据等问题
有可能会产生这种问题。解决方案:在调用send()前,把fd在epoll的epoll_out事件取消掉,发送完后再将fd添加回来到epoll。
4 send返回-1,打开error值是eagain???
产生原因:sendbuffer是满的
解决方案:检测是否准备就绪(可采用epoll,poll,select等)
5 recv的时候,也返回-1
这是正常现象,recvbuffer无data。
6 常见的误区
epoll性能很高,是因为有内存映射mmap。
epoll性能一定比select,poll更高。当fd比较少时候,epoll效率可能没有select和poll高。
2 epoll如何实现
主要分为四部分:
1 如何管理所有的fd(数据结构)
2 如何实现线程安全
3 协议栈如何与epoll关联
4 ET和LT如何实现
1 数据结构
对于fd分为两种状态:就绪(有data)和空闲(无data);注意fd:0,1,2是系统提供的,增加是从3开始增加的。
对于一款服务器来说,一般情况下就绪的比例一般情况下都比较少,空闲的较多。
空闲集合:采用红黑树存储
就绪集合:采用队列存储
空闲集合需要查找所以采用索引的数据结构:hash,红黑树,b树,跳表等;对于就绪集合为何采用队列呢不采用栈:主要是因为就绪集合数据量相对小,而且无查找,无修改,只需要将数据从用户空间搬到内核空间。epoll_wait一次性也拿不完所以采用先进先出的队列来实现。
主要是由两个数据结构epitem和eventpoll。
epitem是每一个io所对应的事件,epoll_ctl添加EPOLL_CTL_ADD时候就会创建一个;
eventpoll是每个epoll所对应的,epoll_create创建的时候就会创建一个eventpoll;
具体的组织结构如下:
2 线程安全
有些情况下,可能会有多个线程同时操作epoll的时候。
解决方案:加锁
list:存储准备就绪IO
红黑树:用来存储所有的IO。
对于数据结构主要是两个方面insert,remove。
当内核数据准备就绪时候,则会执行epoll_event_callback回调函数,将epitem添加到list中;对于删除,当epoll_wait激活重新运行的时候,将list中的epitem一一拷贝copy到events参数中。
红黑树而言,当应用程序执行epoll_ctl添加EPOLL_CTL_ADD操作时候,将epitem添加到rbtree中;对于删除当epoll_ctl删除EPOLL_CTL_DEL操作的时候,将epitem添加到rbtree中。
线程安全的问题转换成了,对红黑树和就绪队列加锁的问题
红黑树加锁主要两种方式:锁整颗树;锁子树。(两者锁子树相对性能高一点,实现起来就很复杂。)
队列加锁:可以采用自旋锁spinlock或者CAS也行。
List:添加:
pthread_spin_lock(&ep->lock); // 获取spinlock
epi->rdy = 1; // epitem的rdy设置为1,代表epitem已经在就绪队列中&#