用户区网络缓冲区
为什么要有用户层缓冲区
TCP内核协议栈,每个连接都有一个接收缓冲区和一个发送缓冲区,为啥用户层也要有:
- 为啥要有接收缓冲区
- 生产者速度大于消费者速度:客户端发送地太快,服务器处理不过来,需要先放到用户态缓冲区
- TCP基于流,可能我接收到的数据部分只是一个包的一部分,需要等待下一个包到达才能处理
- TCP基于流,需要进行粘包处理
- 为啥要有发送缓冲区
- 要发送的数据可能大于发送缓冲区剩余空间, n=send(fd,size),size-n就是需要置入缓冲区的数据
而对于UDP,其内核没有发送缓冲区,如果想实现udp可靠传输,也需要设计用户态发送缓冲区、序列号、确认号、超时重传机制等
其次,UDP虽然基于报文传输,没有粘包问题,但还是需要面临内核协议栈接收缓冲区满的问题,因此,也需要设计用户态的接收缓冲区
用户缓冲区设计
以接收缓冲区为例:
- 定长缓冲区fixbuff:一个数组,当前部分的数据被取走后,后面的数据需要往前移动,很麻烦
- 环形缓冲区ringbuff:逻辑上环形,其实也是一个数组,进行取余操作,当缓冲区满时,追加的数据填充到前端;可以将数组的长度设置为2^n,这样就可以将数组的取余运算转换为位运算
- 有个缺点:数据分散问题,数据可能一部分在数组尾部,一部分在数组头部,需要accept两次
- 假设是发送缓存区,分散的数据要分两次send到内核发送缓冲区,同样是两次系统调用
- 链式缓冲区chainbuff:多个数组链接起来,缓冲区满了时在后面新增一个数组。(这样还是可能产生数据分散的问题,需要accept两次)
chainbuff
其实就是一个可伸缩的数组 =》 和dequeue有点像
off的作用就是记录真实数据的起始地址,因为一个数据包可能是由上一段的后半段和这一段的前半段构成的,读包的时候把前半段也读了 =》 减少数据移动
如果上一段读完了,就free掉buff,把first指针下移
=》 有自己的缓冲区后,就只要操纵自己的缓冲区,而不用操作io =》 怎么把内核态的的数据拷贝到用户态缓冲区呢?epoll监控到可读就拷贝吗?
缓冲区设置最大值,防止ddos攻击,为什么设置为16M,因为mysql是16M,redis更少
构建chaibuff,一个buff块:长度是1024的整数倍,内存分配快点, 比如我要申请2049B的内存,就得分配1024*2^n,其中n使得这个公式大于2049得最小整数
chaibuff的缺点:
协议一般有两种数据读取方式:固定长度读、特殊字符读。 int n=read(fd,buf,sz)
由于内存不连续的问题,可能造成多次io(一般也就是2次),写操作也是的
解决方法:
-
新malloc一块大的缓冲区,将内核的数据先放到这个缓冲区里
-
linux内核:readv、writev => 将缓存区写入你传给我的不连续的内存