0、思考问题:
如何判断send数据,对方有没有接受成功? send返回正数是没有用的。 send函数做的事情是,将应用需要发送的数据拷贝到协议栈中,用户空间拷贝到内核空间,何时发送的数据包是由协议栈自己决定的。 |
1、TCP的粘包和分包
应用层多次send,第一次send,32字节 第二次send,64字节,协议栈发送粘包96字节
解决方案:
方案1、在应用层协议里,协议头里加长度的域
方案2、每个包加分隔符
方案3、发送定长的包,比较低级
协议栈里tcp包先发的先到 序号+延迟确认 保证。
协议栈设置可以取消延迟确认机制,仅限于在网络情况较好的情况下取消该机制。
拷贝方式: 1、直接将内存拷贝到协议栈 2、mov,整块内存映射到协议栈内存中 |
多个线程并发给一个fd调用send,会使send buff里数据混乱。 解决方案:通过io多路复用(epoll、poll、select),epoll判断多线程 把fd从epoll里取到,然后epollout取消,不再监听,另一个线程使用该fd send,send完再加入到epoll里,继续监听。保证只有一个线程占用了可写的fd。 |
1、调用send时候,返回-1,eagein,唯一原因,send buff 是满的。 2、recv时候,fd非阻塞,set,返回-1,正常现象,recv buff没有数据而已。 |
2、epoll具体实现
客户端数量越多epoll性能越好,1024,超过500以上用epoll。
1)、如何管理好这么多fd
fd状态:
空闲状态:不可操作状态,即空闲,没有数据。
就绪状态:可操作,可读可写。
就绪fd是很少的,大部分处于空闲状态。
提供两个集合,就绪集合,空闲集合。
空闲集合:重查找,做索引,可以从以下选择:最合适的是红黑树
a、最快的是hash,查找速度快,缺点不利于扩展,不利于后端fd管理。
b、然后是红黑树(avl),查找性能logN,可以扩展,最小节点,没有太多空间浪费。
c、b/b+树,查找性能 (logN)/M+logM,节点比较大,不适合内存索引。
d、跳表,概率学,节点有很多浪费,一半的节点都没使用。
fd从3开始依次增加,maxfd设置最大值。
就绪集合:用队列存储
为什么使用队列?
没有查找、修改操作,所有都需要遍历,再加上数量相对不多。数组长度固定,不太好。
队列:先进先出,可能一次拿不完数据。栈是不行的。
红黑树如何做到线程安全?
1、锁整棵树
2、锁子树
两者性能差别不大,锁整棵树更容易实现,推荐锁整棵树,用互斥锁。
用什么锁,互斥锁,业务粒度大用互斥锁,业务粒度小用自旋锁。
队列安全:用自旋锁,用cas也可以spinlock。
协议栈哪些地方需要epoll?
三次握手,数据传输,四次挥手过程中哪些地方需要epoll
a、三次握手完成时候,accept将全链接队列中的tcb块和fd绑定一起,可读
b、recvbuff中有数据,延迟ack超时发送时,将数据搬运到recvbuff,可读
c、sendbuff有空间的时候,可写
d、接受到fin的时候,可读
以上四种情况需要通知到epoll,对应的fd是否就绪
协议栈回调epoll模块,有几个动作需要做:
a、查找,从空闲队列中,找到对应的fd,找不到出错;
b、加入就绪队列,fd置为可读;
c、发条件通知,信号,给epoll_wait,让epoll_wait知道就绪队列不为空了。
ET、LE:
ET,recvbuffer中从没有数据到有数据,触发一次。
LE,recvbuffer中一直有数据则一直触发。
recvbuffer:延迟ack确认哪些数据已经收到了,则将这些数据拷贝到recvbuffer中。