1、概述
首先要看TCP/IP协议,涉及到四层:链路层,网络层,传输层,应用层。
其中以太网(Ethernet)的数据帧在链路层
IP包在网络层
TCP或UDP包在传输层
TCP或UDP中的数据(Data)在应用层
它们的关系是 数据帧{IP包{TCP或UDP包{Data}}}
不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理。
在应用程序中我们用到的Data的长度最大是多少,直接取决于底层的限制。
我们从下到上分析一下:
1.在链路层,由以太网的物理特性决定了数据帧的长度为(46+18)-(1500+18),其中的18是数据帧的头和尾,也就是说数据帧的内容最大为1500(不包括帧头和帧尾),即MTU(Maximum Transmission Unit)为1500;
2.在网络层,因为IP包的首部要占用20字节,所以这的MTU为1500-20=1480;
3.在传输层,对于UDP包的首部要占用8字节,所以这的MTU为1480-8=1472;
所以,在应用层,你的Data最大长度为1472。当我们的UDP包中的数据多于MTU(1472)时,发送方的IP层需要分片fragmentation进行传输,而在接收方IP层则需要进行数据报重组,由于UDP是不可靠的传输协议,如果分片丢失导致重组失败,将导致UDP数据包被丢弃。
从上面的分析来看,在普通的局域网环境下,UDP的数据最大为1472字节最好(避免分片重组)。
但在网络编程中,Internet中的路由器可能有设置成不同的值(小于默认值),Internet上的标准MTU值为576,所以Internet的UDP编程时数据长度最好在576-20-8=548字节以内。
2、TCP、UDP数据包最大值的确定
UDP和TCP协议利用端口号实现多项应用同时发送和接收数据。数据通过源端口发送出去,通过目标端口接收。有的网络应用只能使用预留或注册的静态端口;而另外一些网络应用则可以使用未被注册的动态端口。因为UDP和TCP报头使用两个字节存放端口号,所以端口号的有效范围是从0到65535。动态端口的范围是从1024到65535。
MTU最大传输单元,这个最大传输单元实际上和链路层协议有着密切的关系,EthernetII帧的结构DMAC+SMAC+Type+Data+CRC由于以太网传输电气方面的限制,每个以太网帧都有最小的大小64Bytes最大不能超过1518Bytes,对于小于或者大于这个限制的以太网帧我们都可以视之为错误的数据帧,一般的以太网转发设备会丢弃这些数据帧。
由于以太网EthernetII最大的数据帧是1518Bytes这样,刨去以太网帧的帧头(DMAC目的MAC地址48bits=6Bytes+SMAC源MAC地址48bits=6Bytes+Type域2Bytes)14Bytes和帧尾CRC校验部分4Bytes那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes这个值我们就把它称之为MTU。
UDP 包的大小就应该是 1500 - IP头(20) - UDP头(8) = 1472(Bytes)
TCP 包的大小就应该是 1500 - IP头(20) - TCP头(20) = 1460 (Bytes)
注*PPPoE所谓PPPoE就是在以太网上面跑“PPP”。随着宽带接入(这种宽带接入一般为Cable Modem或者xDSL或者以太网的接入),因为以太网缺乏认证计费机制而传统运营商是通过PPP协议来对拨号等接入服务进行认证计费的,所以引入PPPoE。PPPoE导致MTU变小了以太网的MTU是1500,再减去PPP的包头包尾的开销(8Bytes),就变成1492。不过目前大多数的路由设备的MTU都为1500。
如果我们定义的TCP和UDP包没有超过范围,那么我们的包在IP层就不用分包了,这样传输过程中就避免了在IP层组包发生的错误;如果超过范围,既IP数据报大于1500字节,发送方IP层就需要将数据包分成若干片,而接收方IP层就需要进行数据报的重组。更严重的是,如果使用UDP协议,当IP层组包发生错误,那么包就会被丢弃。接收方无法重组数据报,将导致丢弃整个IP数据报。UDP不保证可靠传输;但是TCP发生组包错误时,该包会被重传,保证可靠传输。
UDP数据报的长度是指包括报头和数据部分在内的总字节数,其中报头长度固定,数据部分可变。数据报的最大长度根据操作环境的不同而各异。从理论上说,包含报头在内的数据报的最大长度为65535字节(64K)。
我们在用Socket编程时,UDP协议要求包小于64K。TCP没有限定,TCP包头中就没有“包长度”字段,而完全依靠IP层去处理分帧。这就是为什么TCP常常被称作一种“流协议”的原因,开发者在使用TCP服务的时候,不必去关心数据包的大小,只需讲SOCKET看作一条数据流的入口,往里面放数据就是了,TCP协议本身会进行拥塞/流量控制。
不过鉴于Internet(非局域网)上的标准MTU值为576字节,所以建议在进行Internet的UDP编程时,最好将UDP的数据长度控制在548字节 (576-8-20)以内。
3、TCP、UDP数据包最小值的确定
在用UDP局域网通信时,经常发生“Hello World”来进行测试,但是“Hello World”并不满足最小有效数据(64-46)的要求,为什么小于18个字节,对方仍然可用收到呢?因为在链路层的MAC子层中会进行数据补齐,不足18个字节的用0补齐。但当服务器在公网,客户端在内网,发生小于18个字节的数据,就会出现接收端收不到数据的情况。
以太网EthernetII规定,以太网帧数据域部分最小为46字节,也就是以太网帧最小是6+6+2+46+4=64。除去4个字节的FCS,因此,抓包时就是60字节。当数据字段的长度小于46字节时,MAC子层就会在数据字段的后面填充以满足数据帧长不小于64字节。由于填充数据是由MAC子层负责,也就是设备驱动程序。不同的抓包程序和设备驱动程序所处的优先层次可能不同,抓包程序的优先级可能比设备驱动程序更高,也就是说,我们的抓包程序可能在设备驱动程序还没有填充不到64字节的帧的时候,抓包程序已经捕获了数据。因此不同的抓包工具抓到的数据帧的大小可能不同。下列是本人分别用wireshark和sniffer抓包的结果,对于TCP 的ACK确认帧的大小一个是54字节,一个是60字节,wireshark抓取时没有填充数据段,sniffer抓取时有填充数据段。
4、实际应用
用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) - UDP头(8)=65507字节。用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。
用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。
--------------------------------------------------------------------------------------------------------------
UDP数据包的理论长度
udp数据包的理论长度是多少,合适的udp数据包应该是多少呢?从TCP-IP详解卷一第11章的udp数据包的包头可以看出,udp的最大包长度是2^16-1的个字节。由于udp包头占8个字节,而在ip层进行封装后的ip包头占去20字节,所以这个是udp数据包的最大理论长度是2^16-1-8-20=65507。
然而这个只是udp数据包的最大理论长度。首先,我们知道,TCP/IP通常被认为是一个四层协议系统,包括链路层、网络层、运输层、应用层。UDP属于运输层,在传输过程中,udp包的整体是作为下层协议的数据字段进行传输的,它的长度大小受到下层ip层和数据链路层协议的制约。
MTU相关概念
以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的。这个1500字节被称为链路层的MTU(最大传输单元)。因特网协议允许IP分片,这样就可以将数据包分成足够小的片段以通过那些最大传输单元小于该数据包原始大小的链路了。这一分片过程发生在网络层,它使用的是将分组发送到链路上的网络接口的最大传输单元的值。这个最大传输单元的值就是MTU(Maximum Transmission Unit)。它是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。最大传输单元这个参数通常与通信接口有关(网络接口卡、串口等)。
在因特网协议中,一条因特网传输路径的“路径最大传输单元”被定义为从源地址到目的地址所经过“路径”上的所有IP跳的最大传输单元的最小值。
需要注意的是,loopback的MTU不受上述限制,查看loopback MTU值:
[root@bogon ~]# cat /sys/class/net/lo/mtu
65536
IP分包udp数据包长度的影响
如上所述,由于网络接口卡的制约,mtu的长度被限制在1500字节,这个长度指的是链路层的数据区。对于大于这个数值的分组可能被分片,否则无法发送,而分组交换的网络是不可靠的,存在着丢包。IP 协议的发送方不做重传。接收方只有在收到全部的分片后才能 reassemble并送至上层协议处理代码,否则在应用程序看来这些分组已经被丢弃。
假定同一时刻网络丢包的概率是均等的,那么较大的IP datagram必然有更大的概率被丢弃,因为只要丢失了一个fragment,就导致整个IP datagram接收不到。不超过MTU的分组是不存在分片问题的。
MTU的值并不包括链路层的首部和尾部的18个字节。所以,这个1500字节就是网络层IP数据报的长度限制。因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节。而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的。又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节。这个1472字节就是我们可以使用的字节数。
当我们发送的UDP数据大于1472的时候会怎样呢?这也就是说IP数据报大于1500字节,大于MTU。这个时候发送方IP层就需要分片(fragmentation)。把数据报分成若干片,使每一片都小于MTU。而接收方IP层则需要进行数据报的重组。而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方便无法重组数据报。将导致丢弃整个UDP数据报。因此,在普通的局域网环境下,将UDP的数据控制在1472字节以下为好。
进行Internet编程时则不同,因为Internet上的路由器可能会将MTU设为不同的值。如果我们假定MTU为1500来发送数据的,而途经的某个网络的MTU值小于1500字节,那么系统将会使用一系列的机制来调整MTU值,使数据报能够顺利到达目的地。鉴于Internet上的标准MTU值为576字节,所以在进行Internet的UDP编程时,最好将UDP的数据长度控件在548字节(576-8-20)以内。
udp丢包是指网卡接收到数据包后,linux内核的tcp/ip协议栈在udp数据包处理过程中的丢包,主要原因有两个:
1、udp数据包格式错误或校验和检查失败。
2、应用程序来不及处理udp数据包。
对于原因1,udp数据包本身的错误很少见,应用程序也不可控,本文不讨论。
首先介绍通用的udp丢包检测方法,使用netstat命令,加-su参数。
# netstat -su
Udp:
2495354 packets received
2100876 packets to unknown port received.
3596307 packet receive errors
14412863 packets sent
RcvbufErrors: 3596307
SndbufErrors: 0
从上面的输出中,可以看到有一行输出包含了"packet receive errors",如果每隔一段时间执行netstat -su,发现行首的数字不断变大,表明发生了udp丢包。
下面介绍一下应用程序来不及处理而导致udp丢包的常见原因:
1、linux内核socket缓冲区设的太小
# cat /proc/sys/net/core/rmem_default
# cat /proc/sys/net/core/rmem_max
可以查看socket缓冲区的缺省值和最大值。
rmem_default和rmem_max设置为多大合适呢?如果服务器的性能压力不大,对处理时延也没有很严格的要求,设置为1M左右即可。如果服务器的性能压力较大,或者对处理时延有很严格的要求,则必须谨慎设置rmem_default 和rmem_max,如果设得过小,会导致丢包,如果设得过大,会出现滚雪球。
2、服务器负载过高,占用了大量cpu资源,无法及时处理linux内核socket缓冲区中的udp数据包,导致丢包。
一般来说,服务器负载过高有两个原因:收到的udp包过多;服务器进程存在性能瓶颈。如果收到的udp包过多,就要考虑扩容了。服务器进程存在性能瓶颈属于性能优化的范畴,这里不作过多讨论。
3、磁盘IO忙
服务器有大量IO操作,会导致进程阻塞,cpu都在等待磁盘IO,不能及时处理内核socket缓冲区中的udp数据包。如果业务本身就是IO密集型的,要考虑在架构上进行优化,合理使用缓存降低磁盘IO。
这里有一个容易忽视的问题:很多服务器都有在本地磁盘记录日志的功能,由于运维误操作导致日志记录的级别过高,或者某些错误突然大量出现,使得往磁盘写日志的IO请求量很大,磁盘IO忙,导致udp丢包。
对于运维误操作,可以加强运营环境的管理,防止出错。如果业务确实需要记录大量的日志,可以使用内存log或者远程log。
4、物理内存不够用,出现swap交换
swap交换本质上也是一种磁盘IO忙,因为比较特殊,容易被忽视,所以单列出来。
只要规划好物理内存的使用,并且合理设置系统参数,可以避免这个问题。
5)磁盘满导致无法IO
没有规划好磁盘的使用,监控不到位,导致磁盘被写满后服务器进程无法IO,处于阻塞状态。最根本的办法是规划好磁盘的使用,防止业务数据或日志文件把磁盘塞满,同时加强监控,例如开发一个通用的工具,当磁盘使用率达到80%时就持续告警,留出充足的反应时间。
测试环境
处理器:Intel(R) Xeon(R) CPU X3440 @ 2.53GHz,4核,8超线程,千兆以太网卡,8G内存
模型1
单机,单线程异步UDP服务,无业务逻辑,只有收包操作,除UDP包头外,一个字节数据。
测试结果
进程个数 |
1 |
2 |
4 |
8 |
平均处理速度(包/秒) |
791676.1 |
1016197 |
1395040 |
1491744 |
网卡流量(Mb/s) |
514.361 |
713.786 |
714.375 |
714.036 |
CPU占用情况(%) |
100 |
200 |
325 |
370 |
现象:
1、单机UDP收包处理能力可以每秒达到150w左右。
2、处理能力随着进程个数的增加而增强。
3、在处理达到峰值时,CPU资源并未耗尽。
结论:
1、UDP的处理能力还是非常可观的。
2、对于现象2和现象3,可以看出,性能的瓶颈在网卡,而不在CPU,CPU的增加,处理能力的上升,来源于丢包(UDP_ERROR)个数的减少。
模型2
其他测试条件同模型1,除UDP包头外,一百个字节数据。
测试结果
进程个数 |
1 |
2 |
4 |
8 |
平均处理速度(包/秒) |
571433.4 |
752319.9 |
731545.6 |
751922.5 |
网卡流量(Mb/s) |
855.482 |
855.542 |
855.546 |
855.549 |
CPU占用情况(%) |
100 |
112.9 |
—— |
—— |
现象:
1、100个字节的包大小,比较符合平常的业务情形。
2、UDP的处理能力还是非常可观,单机峰值可以到达每秒75w。
3、在4,8个进程时,没有记录CPU的占用情况(网卡流量耗尽),不过可以肯定的是,CPU未耗尽。
4、随着进程个数的上升,处理能力没有明显提升,但是,丢包(UDP_ERROR)的个数大幅下降。
模型3
单机,单进程,多线程异步UDP服务,多线程共用一个fd,无业务逻辑,除UDP包头外,一个字节数据。
测试结果:
线程个数 |
1 |
2 |
平均处理速度(包/秒) |
791676 |
509868 |
网卡流量(Mb/s) |
514.361 |
714.229 |
CPU占用情况(%) |
100 |
150 |
现象:
1、随着线程个数的增加,处理能力不升反降。
结论:
1、多线程共用一个fd,会造成相当大的锁争用。
2、多线程共用一个fd,当有包来时,会激活所有的线程,导致频繁的上下文切换。
最终结论:
1、UDP处理能力非常可观,在日常的业务情形中,UDP一般不会成为性能瓶颈。
2、随着进程个数的增加,处理能力未明显上升,但是丢包个数明显下降。
3、本次测试过程中,瓶颈在网卡,而不在CPU。
4、采用多进程监听不同端口的模型,而不是多进程或多线程监听同一个端口。
UDP数据包长度 |
在本机(loopback)传输,可以根据需要设置MTU,但记住,UDP最大理论长度65507。 在内网传输,最好控制在1472字节(1500-8-20)。 在internet上传输,最好控制在548字节(576-8-20)以内。 |
UDP收包能力 |
UDP处理能力非常可观,在日常的业务情形中,UDP一般不会成为性能瓶颈。 随着进程个数的增加,处理能力未明显上升,但是丢包个数明显下降。 采用多进程监听不同端口的模型,而不是多进程或多线程监听同一个端口。 |
转自: https://blog.csdn.net/caoshangpa/article/details/51530685
https://www.cnblogs.com/linuxbug/p/4906000.html