TCP的发送缓冲区和接收缓冲区

TCP协议在负责端到端数据传输的过程中,其核心机制涉及到发送端和接收端的操作系统内存管理,具体包括用户空间(user space)与内核空间(kernel space)两部分。对于每一个TCP socket连接,在内核中都会配备一个发送缓冲区和一个接收缓冲区,TCP的全双工通信模式及流量控制、拥塞控制等特性正是基于这两个独立的缓冲区及其填充状态来实现的。

在TCP socket连接的两端,分别存在send和recv两个操作函数。例如,当客户端向服务器发送数据时,客户端进程会调用send函数,该函数的作用是将待发送的数据复制到socket在其内核中的发送缓冲区。值得注意的是,当send函数执行完毕并返回时,并不代表数据已经到达服务端;实际上,send函数仅完成了从应用层缓冲区到socket内核发送缓冲区的数据复制,数据的实际发送过程由TCP协议栈负责,而非send函数直接处理。

而在接收端,接收缓冲区用于暂存接收到的数据,直到应用程序调用recv函数进行读取为止。recv函数的主要职责是从内核缓冲区中复制数据到应用层用户指定的缓冲区,并返回已接收的数据量。若应用程序未及时调用recv读取数据,这些数据将会持续保留在对应的socket接收缓冲区内。对于TCP协议而言,一旦接收缓冲区满载而应用程序仍未读取,接收端会通知发送端,表示当前接收窗口关闭(即窗口大小win变为0),这是TCP滑动窗口机制的具体实践,确保了TCP套接字接收缓冲区不会溢出,从而维持TCP协议的可靠性。因为在TCP中,发送端不能发送超过接收端所通告窗口大小的数据,否则超限部分将会被接收端TCP丢弃,这就构成了TCP的流量控制机制。

举个例子:
TCP协议就像一条高速公路,它帮助我们的电脑之间互相传递信息。我们可以想象每台电脑都有两个箱子,一个叫“发送箱”,另一个叫“接收箱”。

当你通过你的电脑(客户端)想给另一台电脑(服务器)发送信息时,你先把这些信息放到“发送箱”里。这里的”send”就好比是你把信件放入邮箱的动作,但并不意味着信立刻就送到了对方手里。实际上,“send”只是帮你把信息从你的程序空间搬到了操作系统内核的“发送箱”里,真正把信息送出网络的任务是由TCP协议来完成的。

与此同时,另一台电脑也有一个“接收箱”。当信息通过网络传过来后,会被暂时存放在这个“接收箱”中,等待你的程序通过“recv”命令去取出。如果信息一直在“接收箱”里堆积,而你迟迟不去取,那么这个“接收箱”就会逐渐装满。为了防止溢出,TCP协议有一个聪明的办法,就是当“接收箱”快满时,它会告诉发送端:“嘿,我的箱子快满了,你先暂停发送吧。”这就是所谓的TCP流量控制,类似于我们生活中的一种自我调节机制,确保信息能有序且稳定地传输。

最后

在使用美西地区的VPS时,单线程速度完全能够达到较高的水平,若无法实现高速连接,则可能是VPS自身性能问题或是本地宽带环境的影响。由于美西地区距离中国大陆较远,网络延迟相对较高,在这种情况下,Socket通信对缓存的需求会更大。如果按照Linux内核的默认TCP参数设置,可能会导致单线程下的TCP传输速率上限停留在约200Mbps,相较于低延迟的亚太地区VPS,容易让人误以为存在线路质量问题。

实际上,为了提升TCP传输效率,特别是在高延迟条件下,有必要增大TCP缓存大小。这是因为TCP协议遵循流量控制机制,当发送数据包的速度超过了接收端处理并确认的速度时,超出接收窗口大小的数据包将会被丢弃。如果TCP缓存满载而没有及时释放空间,会导致窗口停止滑动,进而触发发送端降低传输速率以适应接收端的实际处理能力。

有些用户反馈称某些VPS提供商表现出更好的网络性能,而这往往是因为这些VPS服务商在其系统模板中预先调整了/etc/sysctl.conf文件中的TCP窗口大小设置,从而优化了长距离、高延迟环境下的数据传输效能。

以下是我在用的参数:

net.core.default_qdisc = fq_codel
net.ipv4.tcp_congestion_control = bbr
net.core.netdev_max_backlog = 16386
net.core.rmem_max=30720000
net.core.wmem_max=30720000
net.ipv4.tcp_rmem=4096 87380 30720000
net.ipv4.tcp_wmem=4096 16384 30720000
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_tw_buckets = 6000
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 6
net.ipv4.tcp_mtu_probing = 1 #启用 MTU 探测
net.ipv4.tcp_sack = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_adv_win_scale = -2
net.ipv4.tcp_window_scaling = 1