缓冲区设计
一、简介
在网络通讯中,用户态缓冲区和内核态缓冲区的大小设定对于优化网络性能和确保数据传输可靠性至关重要。下图是网路通讯的内核缓冲区使用情况:
数据的读写都需要进行系统调用,从用户态切换到内核态去接收数据,结束后需要切换回用户态;
二、网络数据的传输过程:
- 发送
- 应用程序通过系统调用将用户数据拷贝sk_buff,并放到socket的发送缓冲区中
- 网络协议栈从socket法案送缓冲区中取出sk_buffer,并克隆一个新的sk_buffer(用于丢失重传)
- 向下依次增加TCP/UDP头部、IP头部、帧头(MAC头)、帧尾,(tcp层进行数据分段、IP层进行数据分片)
- 触发软件中断,通知网卡驱动程序有新的网络数据需要发送
- 网卡驱动程序从发送队列中取出sk_buffer写到ringbuffer(内存DMA区域)
- 网卡发送数据,发送成功后触发硬件中断,释放sk_buffer和ringbuffer没存
- 当接收到tcp报文的ack应答后释放sk_buffer
- 接收
- 网卡接收到数据包,通过DMA协处理器将数据写入内存ringbuffer结构中
- 网卡向cpu发起硬件中断,cpu收到中断请求,根据中断表查找中断处理函数,进行中断处理
- 中断处理函数将屏蔽中断,发起软件中断
- 内核ksoftirqd软件中断线程负责软件中断处理,该线程从ringbuffer中逐个取出数据帧到sk_buffer
- 从帧头取出ip协议,去掉帧头帧尾
- 根据协议五元组找到socket,并将数据取出放到sicket的接收缓冲区中,软件中断处理结束后开启硬件中断
- 应用程序通过系统调用将socket中的接收缓冲区的数据拷贝到用户层缓冲区
用户态缓冲区(User-Space Buffer)
用户态缓冲区是指在应用程序地址空间中分配的缓冲区,用于存储待发送或已接收的数据。调整用户态缓冲区的大小可以影响应用程序处理数据的能力,特别是在高吞吐量或低延迟要求的场景中。
-
在C/C++中,可以通过setsockopt函数来调整TCP或UDP套接字的发送(SO_SNDBUF)和接收(SO_RCVBUF)缓冲区大小。例如:
1int snd_buff_size = 8192; // 自定义的发送缓冲区大小 2int rcv_buff_size = 16384; // 自定义的接收缓冲区大小 3setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &snd_buff_size, sizeof(snd_buff_size)); 4setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcv_buff_size, sizeof(rcv_buff_size));
-
注意,实际设置的缓冲区大小可能会受到操作系统限制,并且可能不是精确设置的值。有时,内核可能会调整至最接近的允许值,通常是用户请求值的两倍。
三、内核态缓冲区(Kernel-Space Buffer)
内核态缓冲区是操作系统内核管理的一部分,用于在网络接口和用户空间之间暂存数据。它的大小直接影响到网络数据包的处理效率,尤其是在网络拥塞或高流量情况下。
-
对于Linux系统,可以通过修改内核参数来调整某些类型的内核缓冲区大小,例如通过编辑
/etc/sysctl.conf
文件或使用sysctl
命令动态调整。例如,调整网络接收缓冲区的默认最小值和最大值:# 动态调整 sudo sysctl -w net.core.rmem_default=xxxx # 设定默认接收缓冲区大小 sudo sysctl -w net.core.rmem_max=yyyy # 设定最大接收缓冲区大小 # 或者永久修改,在/etc/sysctl.conf中添加: net.core.rmem_default=xxxx net.core.rmem_max=</