TCP/IP学习(四)TCP缓冲区大小及限制

这个问题在前面有的部分已经涉及,这里在重新总结下。主要参考UNIX网络编程。

(1)数据报大小
IPv4的数据报最大大小是65535字节,包括IPv4首部。因为首部中说明大小的字段为16位。
IPv6的数据报最大大小是65575字节,包括40字节的IPv6首部。同样是展16位,但是IPv6首部大小不算在里面,所以总大小比IPv4大一个首部(40字节)。

(2)MTU
许多网络有一个可由硬件规定的MTU。以太网的MTU为1500字节。有一些链路的MTU的MTU可以由认为配置。IPv4要求的最小链路MTU为68字节。这允许最大的IPv4首部(包括20字节的固定长度部分和最多40字节的选项部分)拼接最小的片段(IPv4首部中片段偏移字段以8个字节为单位)IPv6要求的最小链路MTU为1280字节。

(3)分片(fragmentation)
当一个IP数据报从某个接口送出时,如果它的大小超过相应链路的MTU,IPv4和IPv6都将执行分片。这些片段在到达终点之前通常不会被重组(reassembling)。IPv4主机对其产生的数据报执行分片,IPv4路由器则对其转发的数据报进行分片。然后IPv6只有主机对其产生的数据报执行分片,IPv6路由器不对其转发的数据报执行分片。

IPv4首部的“不分片”(do not fragment)位(即DF位)若被设置,那么不管是发送这些数据报的主机还是转发他们的路由器,都不允许对它们分片。当路由器接收到一个超过其外出链路MTU大小且设置了DF位的IPv4数据报时,它将产生一个ICMPv4“destination unreachable,fragmentation needed but DF bit set”(目的不可到达,需分片但DF位已设置)的出错消息。

既然IPv6路由器不执行分片,每个IPv6数据报于是隐含一个DF位。当IPv6路由器接收到一个超过其外出链路MTU大小的IPv6数据报时,它将产生一个ICMPv6 “packet too big”的出错消息。IPv4的DF位和隐含DF位可用于路径MTU发现。

(4)最小重组缓冲区大小(minimum reassembly buffer size)
IPv4和IPv6都定义了最小缓冲区大小,它是IPv4或IPv6任何实现都必须保重支持的最小数据报大小。其值对IPv4为576字节,对于IPv6为1500字节。例如,对于IPv4而言,我们不能判定某个给定的目的能否接受577字节的数据报,为此很多应用避免产生大于这个大小的数据报。

(5)MSS(maximun segment size)
TCP有一个最大分段大小,用于对端TCP通告对端每个分段中能发送的最大TCP数据量。MSS的目的是告诉对端其重组缓冲区大小的实际值,从而避免分片。MSS经常设计成MTU减去IP和TCP首部的固定长度。以太网中使用IPv4MSS值为1460,使用IPv6的MSS值为1440(两者TCP首部都是20字节,但是IPv6首部是40字节,IPv4首部是20字节)。

(6)TCP发送缓冲区
每个TCP套接字有一个发送缓冲区,我们可以用SO_SNDBUF套接字选项来更改该缓冲区的大小。当某个应用进程调用write时,内核从该应用进程的缓冲区复制所有数据到缩写套接字的发送缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区中已有其他数据),该应用进程将被投入睡眠。这里假设该套接字是阻塞的,它通常是默认设置。内核将不从write系统调用返回,直到应用进程缓冲区中的所有数据都复制到套接字发送缓冲区。因此,从写一个TCP套接字的write调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不表明对端的TCP或应用进程已接受到数据。

这一端的TCP提取套接字发送缓冲区中的数据并把它发送给对端的TCP,其过程基于TCP数据传送的所有规则。对端TCP必须确认收到的数据,伴随来自对端的ACK的不断到达,本段TCP至此才能从套接字发送缓冲区中丢弃已确认的数据。TCP必须为已发送的数据保留一个副本,直到它被对端确认为止。本端TCP以MSS大小或是更小的块把数据传递给IP,同时给每个数据块安上一个TCP首部以构成TCP分节,其中MSS或是由对端告知的值,或是536(若未发送一个MSS选项为576-TCP首部-IP首部)。IP给每个TCP分节安上一个IP首部以构成IP数据报,并按照其目的的IP地址查找路由表项以确定外出接口,然后把数据报传递给相应的数据链路。每个数据链路都有一个数据队列,如果该队列已满,那么新到的分组将被丢弃,并沿协议栈向上返回一个错误:从数据链路到IP,在从IP到TCP。TCP将注意到这个错误,并在以后某个时候重传相应的分节。应用程序不知道这种暂时的情况。

(7)UDP发送缓冲区
任何UDP套接字都有发送缓冲区大小(我们可以用SO_SNDBUF套接字选项更改它),不过它仅仅是可写道套接字UDP数据报大小上限。如果一个应用进程写一个大于套接字发送缓冲区大小的数据报,内核将返回该进程一个EMSGSIZE错误。既然UDP是不可靠的,它不必保存应用进程数据的一个副本,因此无需一个真正的发送缓冲区。(应用进程的数据在沿协议栈向下传递时,通常被复制到某种格式的一个内核缓冲区中,然而当该数据被发送之后,这个副本被数据链路层丢弃了。)

UDP简单地给来自用户的数据报安上8字节首部以构成UDP数据报,然后传递给IP。IPv4或IPv6给UDP数据报安上相应的IP首部以构成IP数据报,执行路由操作确定外出接口,然后或者直接把数据报加入数据链路层输出队列(如果适合于MTU),或者分片后在把每个片段加入数据集链路层的输出队列。如果某个UDP进程发送大数据报,那么它们相比TCP应用数据更有可能被分片,因为TCP会把应用数据划分成MSS大小的块,而UDP却没有对等的手段。

从写一个UDP套接字的write调用成功返回表示所写的数据报或其所有片段已被加入数据链路层的输出队列。如果该队列没有足够的空间存放该数据报或它的某个片段,内核通常会返回一个ENOBUFS错误给它的应用进程。有些UDP实现不返回这种错误,这样甚至数据报未经发送就被丢弃的情况进程也不知道。

 

/// add 2014/6/21

在linux下可以修改协议栈改变tcp缓冲相关参数:

修改系统套接字缓冲区


echo 65536 > /proc/sys/net/core/rmem_max
echo 256960 > /proc/sys/net/core/wmem_max
echo 65536 > /proc/sys/net/core/wmen_default

修改tcp接收/发送缓冲区
echo "4096 32768 65536" > /proc/sys/net/ipv4/tcp_rmem
echo "4096 65536 256960" > /proc/sys/net/ipv4/tcp_wmem

修改网络设备接收队列
echo 500 > /proc/sys/net/core/netdev_max_backlog

重传次数
echo 5 > /proc/sys/net/ipv4/tcp_retries2

 

        发现上面的参数都是改小了,既然大的时候视频比较卡,改小了会好么?首先一个问题是缓冲区越大越好么?如果机器处理不过来tcp流量,那么不管缓冲区有多大,早晚会溢出,这就导致,应用层知道的tcp未收到比较晚,因为在缓冲区里面呆了一段时间,而且重传的数据也较大,会早成网络负担比较大,其实看来并不利于整个网络。那么缓冲区改大有什么好去呢?缓冲区改大可以处理突发的大流量数据,不至于画面变化的时候,也就是流量突然增大的时候缓冲区满。那回来看这个问题,既然大的时候视频会卡,那么改小了,让应用层早点知道tcp没有收到而已,对整个网络也就是省了点流量,对实时视频是否卡影响不大,自己的分析,待验证。

        如果怀疑是机器问题,或者是tcp配置问题,可以换一个机器(配置更好的)看看是不是处理不过来请求而造成的延迟,或者用wireshark抓包来统计流量。

 

        以下来自网络,原始出处已经找不到。。

        计算机需要多大内存?当然是越大越好了,这是用户的想法。但是计算机的设计者则必须在成本、实现难度、和取悦客户等几个因素之间进行折中,选取一个最佳平衡点。对计算机来说,其主要依据是产品的市场定位,高端商务PC至少配2G内存,低端学生机配256M就够了。如果用256M RAM的学生机来作复杂的大规模FPGA仿真,可能会发现硬盘的灯一直是亮的,这说明内存已经不够用了,操作系统正在不停的在内存和硬盘之间兑换数据,用大容量的低速硬盘来弥补内存太小的不足,但是代价是计算时间延长了很多倍。路由器是不是也向PC一样,主要依据售价来决定内存配置的大小呢?会不会也是内存越大越好呢?路由器的设计者依据哪些因素来决定内存配置的大小?一般来说,路由器的内存主要用于一下这些方面:

 

(1)用于存储路由器软件指令和静态数据,路由器跟PC不同,PC是只把当前运行的程序装到RAM中,但多数路由器都是一开机就把全部程序都装到 RAM中,一般来说,路由器的程序也不大(几兆到几十兆);(注:此处主要指控制平面的程序,也就是Cisco和Juniper的路由引擎)

 

(2)用于存储动态数据,例如:路由表、OSPF的链路状态数据库等。假如某路由器需要支持最多10万条路由,按照每条路由256字节计算,那么大约需要200M左右内存。

 

(3)用于缓冲数据报文,路由器的工作原理是存储转发。极端情况下,路由器的每个接口,至少需要缓冲一个报文,否则路由器根本不能工作。下面重点讨论这个问题。

 

        一般来说,路由器配置的报文缓冲区都不止一个报文。因为这样也就意味着当有新报文到达的时候,如果前面一个报文正在发送,这个报文缓冲区尚未处于空闲状态,那么新的报文势必将会被丢掉。等前面一个报文发送完了,链路处于空闲状态,但是由于刚才报文已经被丢掉了,也无法利用链路空闲状态。如果被丢掉的报文是TCP报文,那么主机势必将重传这个报文(在该路由器前面的一段线路上传输两次同样的报文),并缩小自己的发送窗口,降低了TCP连接的速率。

 

        也就是说,如果接口的报文缓冲区太小,将导致丢包率高,数据链路利用率低,TCP传输效率低。那么是不是报文缓冲区越大越好呢?也不是,因为报文缓冲区大到一定程度,就不能继续提高数据链路利用率和降低丢包率了。如果这台路由器处于拥塞状态,接收报文的速率远远大于接口的发送带宽,无论多大的报文缓冲区都会被填满,而报文缓冲区大了,那么也就意味着拥塞状态的时候,报文的转发延迟时间会很长。延迟时间太长的报文,对于接收方来说,已经没有意义了。以 TCP连接为例,当报文大于发送方的重传时间的时候,发送方就会重传该报文,也就是说,大于TCP的重传时间的到达的报文,是没有意义的。对VoIP等应用来说,对网络延时更加敏感。

 

        一般来说,路由器的接口缓冲区的大小有一个经验法则(rule-of-thumb):B = C * RTT,C是链路速率,RTT是平均报文往返之间。至于这个经验法则源自哪里,我没有认真考证。但这个经验法则的主要依据是最大化TCP效率,最大化网络接口带宽利用率。如果依据这个法则来设计路由器,对中低端路由器来说,问题不大。但是对于高端路由器,是有挑战的。一般中高端Internet骨干路由器上会假设RTT为250ms,那么对于个 10GE接口,需要的内存是(10G bit/s * 0.25 s) / 8 约为300MB。也许大家会说,300M不大么,但是可以预见,最近两年核心路由器的容量必将发展到单槽位80 - 160G,也就是说单大约需要2.5G - 5G内存。虽然不是完全不可实现,但还是有一定难度。从 Juniper的一个白皮书(Characteristics of Switches and Routers)可以看出,Juniper也是按照这个经验法则设计的。

 

        但是最近的一些研究认为(sizing router buffers, Guido Appenzler, Isaac Keslassy, Nick McKeown),其实路由器不需要那么大的内存,每个端口只需要缓冲几十个报文就足够了,这样用NP或ASIC内嵌的RAM就够了,不用配置外部RAM。他主要依据是以前的经验法则是根据单TCP流来推算的,作者认为这个模型不对,实际的骨干路由器上是有很多TCP流的,因此应该按照 B = C * RTT / sqrt(N)来计算,N是TCP流数量。但是另外一些研究则认为这个结论不对,路由器上不能只考虑TCP,还有很多急于 UDP的语音和视频应用。反正在教授们之间,这个问题至今仍然没有一致的意见。工程师已经不再争论这个问题了,就按照B = C * RTT来设计,成本可以接受,而且也比较安全。

 

        为了纠正网络问题,有时候需要重新配置网卡的默认IP包大小。经常会发生路由的最大IP包大小比网卡的小。TCP协议可以自适应,但是UDP协议不行(会导致分片丢包,然后重传)。所以NFS over UDP特别要注意设置MTU的大小。可以用命令*tracepath*来看网络上的MTU值,用ifconfig命令来看网卡的MTU值,要使两者匹配。(大部分网络都是1500,除非设置了支持大包)时,IP包在用UDP协议传输时会分片。大量IP包分片会消耗网络两端大量的CPU资源,而且还会导致网络通信更不稳定(因为完整的RPC在UDP分片的任何一个包丢失时都得整个RPC重传)。在2.2和2.4内核中,默认的socket读缓存rmem_default是64k,写缓冲wmem_default是8k。这两个值对有大量读写负载的情况很重要。

 

 

你可能感兴趣的:(TCP与服务器)