用户区网络缓冲区

167 阅读3分钟

用户区网络缓冲区

为什么要有用户层缓冲区

TCP内核协议栈,每个连接都有一个接收缓冲区和一个发送缓冲区,为啥用户层也要有:

  1. 为啥要有接收缓冲区
    • 生产者速度大于消费者速度:客户端发送地太快,服务器处理不过来,需要先放到用户态缓冲区
    • TCP基于流,可能我接收到的数据部分只是一个包的一部分,需要等待下一个包到达才能处理
    • TCP基于流,需要进行粘包处理
  2. 为啥要有发送缓冲区
    • 要发送的数据可能大于发送缓冲区剩余空间, n=send(fd,size),size-n就是需要置入缓冲区的数据

而对于UDP,其内核没有发送缓冲区,如果想实现udp可靠传输,也需要设计用户态发送缓冲区、序列号、确认号、超时重传机制等

其次,UDP虽然基于报文传输,没有粘包问题,但还是需要面临内核协议栈接收缓冲区满的问题,因此,也需要设计用户态的接收缓冲区

用户缓冲区设计

以接收缓冲区为例:

  1. 定长缓冲区fixbuff:一个数组,当前部分的数据被取走后,后面的数据需要往前移动,很麻烦
  2. 环形缓冲区ringbuff:逻辑上环形,其实也是一个数组,进行取余操作,当缓冲区满时,追加的数据填充到前端;可以将数组的长度设置为2^n,这样就可以将数组的取余运算转换为位运算
    • 有个缺点:数据分散问题,数据可能一部分在数组尾部,一部分在数组头部,需要accept两次
    • 假设是发送缓存区,分散的数据要分两次send到内核发送缓冲区,同样是两次系统调用
  3. 链式缓冲区chainbuff:多个数组链接起来,缓冲区满了时在后面新增一个数组。(这样还是可能产生数据分散的问题,需要accept两次)

chainbuff

20230227224756

其实就是一个可伸缩的数组 =》 和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次),写操作也是的

解决方法:

  1. 新malloc一块大的缓冲区,将内核的数据先放到这个缓冲区里

  2. linux内核:readv、writev => 将缓存区写入你传给我的不连续的内存