大数跨境
0
0

Linux TCP收发包配置解析

Linux TCP收发包配置解析 通信行业搬砖工
2023-05-21
0
导读:基于Linux上开发的网络程序,在TCP收发包过程中受哪些因素的影响呢?本文将着重介绍这方面的内容。

作者系水厂大佬高级开发专家,如需要了解更多,欢迎访问https://zhuanlan.zhihu.com/p/619826703

1 前言

在基于Linux上开发的网络程序,在TCP收发包过程中受哪些因素的影响呢?而且我们在工作中又经常会遇到一些和此相关的问题,我们又会需要从这些因素入手去排查问题,所以在定位网络连接这类的问题之前,我们一定要了解这些因素都有哪些。

2 数据包发送过程

数据包的发送从上往下经过了三层:用户态空间的应用、系统内核空间、最后到网卡驱动。应用先将数据写入 TCP sendbuffer ,TCP 层将 sendbuffer 中的数据构建成数据包转交给 IP 层。经过IP层处理后,Link层会将待发送的数据包放入队列 QDisc 。数据包成功放入 QDisc 后,指向数据包的描述符 sk_buff 被放入Ring Buffer输出队列,随后网卡驱动调用 DMA engine 将数据发送到网络链路上。

2.1 net.ipv4.tcp_wmem

应用程序调用write()或者send()系列系统调用开始往外发包时,这些系统调用会把数据包从用户缓冲区拷贝到TCP发送缓冲区(TCP Send Buffer),这个TCP发送缓冲区的大小是受限制的,TCP发送缓冲区的大小默认是受net.ipv4.tcp_wmem来控制:

net.ipv4.tcp_wmem = 4096	16384	4194304

tcp_wmem中这三个数字的含义分别为min、default、max。TCP发送缓冲区的大小会在min和max之间动态调整,初始的大小是default,这个动态调整的过程是由内核自动来做的,应用程序无法干预。自动调整的目的,是为了在尽可能少的浪费内存的情况下来满足发包的需要。

2.2 net.core.wmem_max

tcp_wmem中的max不能超过net.core.wmem_max这个配置项的值,如果超过了,TCP 发送缓冲区最大就是net.core.wmem_max。通常情况下,我们需要设置net.core.wmem_max的值大于等于net.ipv4.tcp_wmem的max:

net.core.wmem_max = 9265836

应用程序有的时候会很明确地知道自己发送多大的数据,需要多大的TCP发送缓冲区,这个时候就可以通过setsockopt()里的SO_SNDBUF来设置固定的缓冲区大小。一旦进行了这种设置后,tcp_wmem就会失效,而且这个缓冲区大小设置的是固定值,内核也不会对它进行动态调整。

但是,SO_SNDBUF设置的最大值不能超过net.core.wmem_max,如果超过了该值,内核会把它强制设置为net.core.wmem_max。所以,想要设置SO_SNDBUF,一定要确认好net.core.wmem_max是否满足需求,否则你的设置可能发挥不了作用。通常情况下,我们都不会通过SO_SNDBUF来设置TCP发送缓冲区的大小,而是使用内核设置的tcp_wmem,因为如果SO_SNDBUF设置得太大就会浪费内存,设置得太小又会引起缓冲区不足的问题。

2.3 net.core.tcp_mem

tcp_wmem以及wmem_max的大小设置都是针对单个TCP连接的,这两个值的单位都是Byte(字节)。系统中可能会存在非常多的TCP连接,如果TCP连接太多,就可能导致内存耗尽。因此,所有TCP连接消耗的总内存也有限制:

net.ipv4.tcp_mem = 4632918	6177225	9265836

与前两个选项不同的是,该选项中这些值的单位是Page(页数),也就是4K。它也有3个值:min、pressure、max。当所有TCP连接消耗的内存总和达到max后,也会因达到限制而无法再往外发包。

2.4 net.ipv4.ip_local_port_range

继续往下来到了IP层。IP层这里容易触发问题的地方是net.ipv4.ip_local_port_range这个配置选项,它是指和其他服务器建立IP连接时本地端口(local port)的范围。曾经遇到过遇到过默认的端口范围太小,以致于无法创建新连接的问题。所以通常情况下,我们都会扩大默认的端口范围:

net.ipv4.ip_local_port_range = 10240	65000

2.5 txqueuelen

为了能够对TCP/IP数据流进行流控,Linux内核在IP层实现了qdisc(排队规则)。我们平时用到的TC就是基于qdisc的流控工具。qdisc的队列长度是我们用ifconfig来看到的txqueuelen,我们在生产环境中也遇到过因为txqueuelen太小导致数据包被丢弃的情况,这类问题可以通过下面这个命令来观察:

$ip -s -s link ls dev eth0
2: eth0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP mode DEFAULT group default qlen 1000
link/ether e8:61:1f:24:ca:9a brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped overrun mcast
919570959639227 618391704197 0 0 0 76353148
RX errors: length crc frame fifo missed nohandler
0 0 0 0 115894 1
TX: bytes packets errors dropped carrier collsns
11988756571022 75073396892 0 0 0 0
TX errors: aborted fifo window heartbeat transns
0 0 0 0 2

dropped这一项不为0,那就有可能是txqueuelen太小导致的,你需要增加这个值的大小

$ ifconfig eth0 txqueuelen 2000
$ ip link set eth0 txqueuelen 2000

2.6 default_qdisc

Linux系统默认的qdisc为pfifo_fast(先进先出),通常情况下我们无需调整它。如果想使用TCP BBR来改善TCP拥塞控制的话,那就需要将它调整为fq(公平队列)

net.core.default_qdisc = fq

3 数据包接收过程

从下往上经过了三层:网卡驱动、系统内核空间,最后到用户态空间的应用。Linux 内核使用 sk_buff 数据结构描述一个数据包。当一个新的数据包到达,NIC 调用 DMA engine ,通过Ring Buffer将数据包放置到内核内存区。Ring Buffer的大小固定,它不包含实际的数据包,而是包含了指向 sk_buff 的描述符。当Ring Buffer满的时候,新来的数据包将给丢弃。一旦数据包被成功接收, 发起中断,由内核的中断处理程序将数据包传递给 Link 层。经过 Link层和IP层的处理,数据包被放入队列等待 TCP 层处理。每个数据包经过 TCP 层一系列复杂的步骤,更新 TCP 状态机,最终到达 recvBuffer ,等待被应用接收处理。有一点需要注意,数据包到达 recvBuffer ,TCP 就会回 ACK 确认,即 TCP 的 ACK 表示数据包已经被操作系统内核收到,但并不确保应用层一定收到数据(例如这个时候系统 crash),因此一般建议应用协议层也要设计自己的确认机制。

3.1 net.core.netdev_budget

TCP数据包的接收流程在整体上与发送流程类似,只是方向是相反的。数据包到达网卡后,就会触发中断(IRQ)来告诉CPU读取这个数据包。但是在高性能网络场景下,数据包的数量会非常大,如果每来一个数据包都要产生一个中断,那CPU的处理效率就会大打折扣,所以就产生了NAPI(New API)这种机制让CPU一次性地去轮询(poll)多个数据包,以批量处理的方式来提升效率,降低网卡中断带来的性能开销。

那在poll的过程中,一次可以poll多少个呢?这个poll的个数可以通过sysctl选项来控制:

net.core.netdev_budget = 300

该控制选项的默认值是300,在网络吞吐量较大的场景中,我们可以适当地增大该值,比如增大到600。增大该值可以一次性地处理更多的数据包。但是这种调整也是有缺陷的,因为这会导致CPU在这里poll的时间增加,如果系统中运行的任务很多的话,其他任务的调度延迟就会增加。

3.2 net.ipv4.tcp_rmem

与 TCP发送缓冲区类似,TCP接收缓冲区的大小也是受控制的。通常情况下,默认都是使用tcp_rmem来控制缓冲区的大小。

net.ipv4.tcp_rmem = 4096	87380	62914

它也有3个字段:min、default、max。TCP接收缓冲区大小也是在min和max之间动态调整 。

3.3 net.ipv4.tcp_moderate_rcvbuf

不过跟发送缓冲区不同的是,这个动态调整是可以通过控制选项来关闭的,这个选项是tcp_moderate_rcvbuf 。通常我们都是打开它,这也是它的默认值:

net.ipv4.tcp_moderate_rcvbuf = 1

之所以接收缓冲区有选项可以控制自动调节,而发送缓冲区没有,那是因为TCP接收缓冲区会直接影响TCP拥塞控制,进而影响到对端的发包,所以使用该控制选项可以更加灵活地控制对端的发包行为。

除了tcp_moderate_rcvbuf 可以控制TCP接收缓冲区的动态调节外,也可以通过setsockopt()中的配置选项SO_RCVBUF来控制,这与TCP发送缓冲区是类似的。如果应用程序设置了SO_RCVBUF这个标记,那么TCP接收缓冲区的动态调整就是关闭,即使tcp_moderate_rcvbuf为1,接收缓冲区的大小始终就为设置的SO_RCVBUF这个值。

也就是说,只有在tcp_moderate_rcvbuf为1,并且应用程序没有通过SO_RCVBUF来配置缓冲区大小的情况下,TCP接收缓冲区才会动态调节。

3.4 net.core.rmem_max

同样地,与TCP发送缓冲区类似,SO_RCVBUF设置的值最大也不能超过net.core.rmem_max。通常情况下,我们也需要设置net.core.rmem_max的值大于等于net.ipv4.tcp_rmem的max:

net.core.rmem_max = 212992


关于小编:

如果您觉得对您有帮助,欢迎关注一下小编。


【声明】内容源于网络
0
0
通信行业搬砖工
14年通信研发经验,大厂搬砖,分享通信工程技术、经验、行业趋势等内容。
内容 503
粉丝 0
通信行业搬砖工 14年通信研发经验,大厂搬砖,分享通信工程技术、经验、行业趋势等内容。
总阅读103
粉丝0
内容503