此前的文章中,我们介绍了 tcp 协议的基本概念和连接的建立与终止 最后,我们介绍了“经受时延的确认”,这是一种将 ACK 包与下一条数据包合并发送的策略,这样可以尽量减少发往网络的报文,以提高传输的效率,节省网络资源。 除此之外,TCP 还有很多其他算法和策略用来优化网络的使用。
Nagle 算法是一种减少 TCP/IP 网络拥塞控制的算法,主要用来解决小包问题。 即使只发送一个字符,传输上也需要 41 字节的包,造成了 4000%,大量小包会导致网络过载,大量小包在负载重的环境下会导致包丢失,妨碍传输速度,导致吞吐量下降。 Nagle 算法在确认数据发送时把数据放入缓存中,直到上一条数据被确认才会发送新的数据。 Nagle 算法保证一个 TCP 连接上最多只有一个未被确认的未完成小分组,在该分组被确认前不能发送其他小分组。
1. 如果包长度达到 MSS(最长报文大小),则允许发送; 2. 如果该包含有FIN,则允许发送; 3. 设置了 TCP_NODELAY 选项,则允许发送; 4. 未设置 TCP_CORK 选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送; 5. 上述条件都未满足,但发生了超时(一般为200ms),则立即发送
滑动窗口协议是一种常用的 TCP 流量控制方法,他允许发送方在停止并等待确认前可以连续发送多个分组。 由于发送方不再需要在每个分组发送后都停下来等待 ACK,而是通过发送多个分组后再进行等待统一的 ACK,这样加速了数据的传输。 在滑动窗口协议中,一个 ACK 可以确认若干个分组,ACK 包的确认分组号参数表示到该分组号-1 为止的所有分组都确认收到。
在 TCP 报文中,我们看到有一个窗口大小选项: 传输控制协议 — TCP 它用来通知对方下一次确认前最多能够接收的字节数,如果通告窗口大小为 0 (零窗口)或小于下一次发送报文的字节数,则发送方必须停止发送,等待确认。 在下一次确认前,接收方也可以发送通过“窗口更新”报文来更新窗口的大小,以使发送方可以在接收到下一次 ACK 前可以再次发送报文。
下图展示了 TCP 滑动窗口协议:
每当报文被确认,窗口都会向右移动,因此而被形象的称为“滑动窗口”。 有三个术语被用来描述窗口的变化:
对于 FTP 等成块数据流发送的 TCP 应用程序来说,窗口大小的变动对程序性能的影响非常明显,当然,如果缓冲区设置太大则会造成内存资源不必要的浪费,恰到好处的大小是刚好可以利用带宽。 “时延带宽积”(Bandwidth Delay Product)表示链接带宽(报文在网络上传输的速率)和往返时间(RTT)的积: BDP = link_bandwidth RTT。 也就是说,如果一个应用程序是通过 100Mbps 的局域网进行通信,其 RTT 为 50ms,那么: BDP = 100Mbps/8 0.05sec = 0.625MB。 我们可以将 TCP 窗口设置为 BDP 或 2*BDP。 linux 2.6 默认窗口大小是 110KB,通过计算,可以知道它限制了带宽为 2.2MBps,也就限制了吞吐量,可见我们主动设置 TCP 窗口大小的必要性。 在 linux 中,通过修改下列配置文件 TCP/IP 参数可以实现自动配置 TCP/IP 参数的功能:
可调节的参数 | 默认值 | 选项说明 |
---|---|---|
/proc/sys/net/core/rmem_default | 110592 | 定义默认的接收窗口大小;对于更大的 BDP 来说,这个大小也应该更大 |
/proc/sys/net/core/rmem_max | 110592 | 定义接收窗口的最大大小;对于更大的 BDP 来说,这个大小也应该更大 |
/proc/sys/net/core/wmem_default | 110592 | 定义默认的发送窗口大小;对于更大的 BDP 来说,这个大小也应该更大 |
/proc/sys/net/core/wmem_max | 110592 | 定义发送窗口的最大大小;对于更大的 BDP 来说,这个大小也应该更大 |
/proc/sys/net/ipv4/tcp_window_scaling | 1 | 启用 RFC 1323 定义的 window scaling;要支持超过 64KB 的窗口,必须启用该值 |
/proc/sys/net/ipv4/tcp_sack | 1 | 启用有选择的应答(Selective Acknowledgment),这可以通过有选择地应答乱序接收到的报文来提高性能(这样可以让发送者只发送丢失的报文段);(对于广域网通信来说)这个选项应该启用,但是这会增加对 CPU 的占用 |
/proc/sys/net/ipv4/tcp_fack | 1 | 启用转发应答(Forward Acknowledgment),这可以进行有选择应答(SACK)从而减少拥塞情况的发生;这个选项也应该启用 |
/proc/sys/net/ipv4/tcp_timestamps | 1 | 以一种比重发超时更精确的方法(请参阅 RFC 1323)来启用对 RTT 的计算;为了实现更好的性能应该启用这个选项 |
/proc/sys/net/ipv4/tcp_mem | 24576 32768 49152 | 确定 TCP 栈应该如何反映内存使用;每个值的单位都是内存页(通常是 4KB)第一个值是内存使用的下限第二个值是内存压力模式开始对缓冲区使用应用压力的上限第三个值是内存上限在这个层次上可以将报文丢弃,从而减少对内存的使用对于较大的 BDP 可以增大这些值(但是要记住,其单位是内存页,而不是字节) |
/proc/sys/net/ipv4/tcp_wmem | 4096 16384 131072 | 为自动调优定义每个 socket 使用的内存第一个值是为 socket 的发送缓冲区分配的最少字节数第二个值是默认值(该值会被 wmem_default 覆盖),缓冲区在系统负载不重的情况下可以增长到这个值第三个值是发送缓冲区空间的最大字节数(该值会被 wmem_max 覆盖) |
/proc/sys/net/ipv4/tcp_rmem | 4096 87380 174760 | 与 tcp_wmem 类似,不过它表示的是为自动调优所使用的接收缓冲区的值 |
/proc/sys/net/ipv4/tcp_low_latency | 0 | 允许 TCP/IP 栈适应在高吞吐量情况下低延时的情况;这个选项应该禁用 |
/proc/sys/net/ipv4/tcp_westwood | 0 | 启用发送者端的拥塞控制算法,它可以维护对吞吐量的评估,并试图对带宽的整体利用情况进行优化;对于 WAN 通信来说应该启用这个选项 |
/proc/sys/net/ipv4/tcp_bic | 1 | 为快速长距离网络启用 Binary Increase Congestion;这样可以更好地利用以 GB 速度进行操作的链接;对于 WAN 通信应该启用这个选项 |
想要修改相应的参数,只需: echo 256960 > /proc/sys/net/core/rmem_default。 echo 256960 > /proc/sys/net/core/rmem_max。 echo 256960 > /proc/sys/net/core/wmem_default。 echo 256960 > /proc/sys/net/core/wmem_max。 echo 0 > /proc/sys/net/ipv4/tcp_timestamps。 echo 1 > /proc/sys/net/ipv4/tcp_sack。 echo 1 > /proc/sys/net/ipv4/tcp_window_scaling。
linux 最大只能支持 64KB 的窗口大小,当启用 window scaling 扩展后,可以实现 32位窗口大小的设置来突破这一限制,因此,如果需要设置 64KB 以上的窗口大小,则必须将 /proc/sys/net/ipv4/tcp_window_scaling 设置为 1。
下面的代码设置了缓冲区大小:
int ret, sock, sock_buf_size;
sock = socket(AF_INET, SOCK_STREAM, 0);
sock_buf_size = BDP;
ret = setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
(char *)&sock_buf_size, sizeof(sock_buf_size));
ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
(char *)&sock_buf_size, sizeof(sock_buf_size));
在 Linux 2.6 内核中,发送缓冲区的大小是由调用用户来定义的,但是接收缓冲区会自动加倍,同时,缓冲区的实际大小还是受限于上面说的系统的相关配置。