首先要看TCP/IP协议,涉及到四层:链路层,网络层。传输层,应用层。linux
当中以太网(Ethernet)的数据帧在链路层
IP包在网络层
TCP或UDP包在传输层
TCP或UDP中的数据(Data)在应用层
它们的关系是 数据帧{IP包{TCP或UDP包{Data}}}
---------------------------------------------------------------------------------
在应用程序中咱们用到的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字节之内。
---------------------------------------------------------------------------------
MTU对咱们的UDP编程很是重要。那怎样查看路由的MTU值呢?
对于windows OS: ping -f -l 如:ping -f -l 1472 192.168.0.1
假设提示:Packets needs to be fragmented but DF set. 则代表MTU小于1500,不断改小data_length值,可以终于測算出gateway的MTU值;
对于linux OS: ping -c -M do -s 如: ping -c 1 -M do -s 1472 192.168.0.1
假设提示 Frag needed and DF set…… 则代表MTU小于1500。可以再測以推算gateway的MTU。编程
--------------------------------------------------------------------------------- windows
IP数据包的最大长度是64K字节(65535),因为在IP包头中用2个字节描写叙述报文长度,2个字节所能表达的最大数字就是65535。
因为IP协议提供为上层协议切割和重组报文的功能,所以传输层协议的数据包长度原则上来讲没有限制。缓存
实际上限制仍是有的,因为IP包的标识字段终究不可能无限长,依照IPv4。好像上限应该是4G(64K*64K)。网络
依靠这样的机制。TCP包头中就没有“包长度”字段。而全然依靠IP层去处理分帧。socket
这就是为何TCP常常被称做一种“流协议”的缘由。开发人员在使用TCP服务的时候,没必要去关心数据包的大小。仅仅需讲SOCKET看做一条数据流的入口。往里面放数据就是了,TCP协议自己会进行拥塞/流量控制。函数
UDP则与TCP不一样,UDP包头内有总长度字段。相同为两个字节,所以UDP数据包的总长度被限制为65535,这样刚好可以放进一个IP包内,使得UDP/IP协议栈的实现很easy和高效。65535再减去UDP头自己所占领的8个字节。UDP服务中的最大有效载荷长度仅为65527。post
这个值也就是你在调用getsockopt()时指定SO_MAX_MSG_SIZE所获得返回值,不论什么使用SOCK_DGRAM属性的socket,一次send的数据都不能超过这个值,不然一定获得一个错误。
那么,IP包提交给下层协议时将会获得如何的处理呢?这就取决于数据链路层协议了,通常的数据链路层协议都会负责将IP包切割成更小的帧,而后在目的端重组它。在EtherNet上,数据链路帧的大小如以上几位大侠所言。而假设是IP over ATM,则IP包将被切分红一个一个的ATM Cell,大小为53字节。
一些典型的MTU值:
网络: MTU字节
超通道 65535
16Mb/s信息令牌环(IBM) 17914
4Mb/s令牌环(IEEE802.5) 4464
FDDI 4352
以太网 1500
IEEE802.3/802.2 1492
X.25 576
点对点(低时延) 296
路径MTU:假设两台主机之间的通讯要经过多个网络,那么每个网络的链路层就可能有不一样的MTU。重要的不是两台主机所在网络的MTU的值,重要的是两台通讯主机路径中的最小MTU。它被称做路径MTU。
Tcp传输中的nagle算法
TCP/IP协议中。无论发送多少数据。老是要在数据前面加上协议头,同一时候,对方接收到数据。也需要发送ACK表示确认。为了尽量的利用网络带宽。TCP老是但愿尽量的发送足够大的数据。
(一个链接会设置MSS參数,所以。TCP/IP但愿每次均可以以MSS尺寸的数据块来发送数据)。
Nagle算法就是为了尽量发送大块数据,避免网络中充斥着不少小数据块。
Nagle算法的基本定义是随意时刻,最多仅仅能有一个未被确认的小段。
所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”。是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
1. Nagle算法的规则:
(1)假设包长度达到MSS,则赞成发送。
(2)假设该包括有FIN。则赞成发送。
(3)设置了TCP_NODELAY选项,则赞成发送。
(4)未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则赞成发送;
(5)上述条件都未知足,但发生了超时(通常为200ms),则立刻发送。
Nagle算法仅仅赞成一个未被ACK的包存在于网络,它并没有论包的大小,所以它其实就是一个扩展的停-等协议。仅仅只是它是基于包停-等的,而不是基于字节停-等的。Nagle算法全然由TCP协议的ACK机制决定,这会带来一些问题,比方假设对端ACK回复很是快的话,Nagle其实不会拼接太多的数据包,尽管避免了网络拥塞。网络总体的利用率依旧很是低。
Nagle算法是silly window syndrome(SWS)预防算法的一个半集。SWS算法预防发送少许的数据,Nagle算法是其在发送方的实现。而接收方要作的时不要通告缓冲空间的很是小增加。不通知小窗体,除非缓冲区空间有显著的增加。这里显著的增加定义为全然大小的段(MSS)或增加到大于最大窗体的一半。
注意:BSD的实现是赞成在空暇连接上发送大的写操做剩下的最后的小段,也就是说,当超过1个MSS数据发送时,内核先依次发送完n个MSS的数据包,而后再发送尾部的小数据包,其间再也不延时等待。
(若是网络不堵塞且接收窗体足够大)。
举个样例,一開始client端调用socket的write操做将一个int型数据(称为A块)写入到网络中,由于此时链接是空暇的(也就是说尚未未被确认的小段),所以这个int型数据会被当即发送到server端,接着,client端又调用write操做写入‘\r\n’(简称B块)。这个时候。A块的ACK没有返回。因此可以以为已经存在了一个未被确认的小段,因此B块没有当即被发送,一直等待A块的ACK收到(大概40ms以后),B块才被发送。整个过程如图所看到的:
这里还隐藏了一个问题,就是A块数据的ACK为何40ms以后才收到?这是因为TCP/IP中不惟独nagle算法。另外一个TCP确认延迟机制 。当Server端收到数据以后,它并不会当即向client端发送ACK,而是会将ACK的发送延迟一段时间(若是为t)。它但愿在t时间内server端会向client端发送应答数据,这样ACK就行和应答数据一块儿发送,就像是应答数据捎带着ACK过去。在我以前的时间中,t大概就是40ms。这就解释了为何'\r\n'(B块)老是在A块以后40ms才发出。
固然。TCP确认延迟40ms并不是一直不变的。TCP链接的延迟确认时间通常初始化为最小值40ms,随后依据链接的重传超时时间(RTO)、上次收到数据包与本次接收数据包的时间间隔等參数进行不断调整。
另外可以经过设置TCP_QUICKACK选项来取消确认延迟。
关于TCP确认延迟的具体介绍可參考:http://blog.csdn.net/turkeyzhou/article/details/6764389
2. TCP_NODELAY 选项
默认状况下,发送数据採用Negale 算法。这样尽管提升了网络吞吐量,但是实时性却减小了。在一些交互性很是强的应用程序来讲是不一样意的,使用TCP_NODELAY选项可以禁止Negale 算法。
此时。应用程序向内核递交的每个数据包都会立刻发送出去。
需要注意的是,尽管禁止了Negale 算法。但网络的传输仍然受到TCP确认延迟机制的影响。
3. TCP_CORK 选项
所谓的CORK就是塞子的意思,形象地理解就是用CORK将链接塞住,使得数据先不发出去,等到拔去塞子后再发出去。设置该选项后。内核会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去,固然若必定时间后(通常为200ms,该值尚待确认),内核仍然没有组合成一个MTU时也必须发送现有的数据(不可能让数据一直等待吧)。
然而。TCP_CORK的实现可能并不像你想象的那么完美,CORK并不会将链接全然塞住。
内核事实上并不知道应用层究竟何时会发送第二批数据用于和第一批数据拼接以达到MTU的大小。所以内核会给出一个时间限制,在该时间内没有拼接成一个大包(努力接近MTU)的话,内核就会无条件发送。也就是说若应用层程序发送小包数据的间隔不够短时,TCP_CORK就没有一点做用,反而失去了数据的实时性(每个小包数据都会延时必定时间再发送)。
4. Nagle算法与CORK算法差异
Nagle算法和CORK算法很是相似。但是它们的着眼点不同,Nagle算法主要避免网络因为太多的小包(协议头的比例很是之大)而拥塞,而CORK算法则是为了提升网络的利用率,使得总体上协议头占用的比例尽量的小。如此看来这两者在避免发送小包上是一致的,在用户控制的层面上。Nagle算法全然不受用户socket的控制,你仅仅能简单的设置TCP_NODELAY而禁用它,CORK算法相同也是经过设置或者清除TCP_CORK使能或者禁用之,然而Nagle算法关心的是网络拥塞问题。仅仅要所有的ACK回来则发包。而CORK算法却可以关心内容,在先后数据包发送间隔很是短的前提下(很是重要。不然内核会帮你将分散的包发出),即便你是分散发送多个小数据包,你也可以经过使能CORK算法将这些内容拼接在一个包内,假设此时用Nagle算法的话,则可能作不到这一点。
实际上Nagle算法并不是很是复杂。他的主要职责是数据的累积,实际上有两个门槛:一个就是缓 冲区中的字节数达到了必定量,还有一个就是等待了必定的时间(通常的Nagle算法都是等待200ms)。这两个门槛的不论什么一个达到都必须发送数据了。通常 状况下。假设数据流量很是大,第二个条件是永远不会起做用的,但当发送小的数据包时,第二个门槛就发挥做用了。防止数据被无限的缓存在缓冲区不是好事情哦。 了解了TCP的Nagle算法的原理以后咱们可以本身动手来实现一个相似的算法了,在动手以前咱们还要记住一个重要的事情,也是咱们动手实现Nagle算 法的主要动机就是我想要紧急发送数据的时候就要发送了,因此对于上面的两个门槛以外还的添加一个门槛就是紧急数据发送。
对于我现在每秒钟10次数据发送。每次数据发送量固定在85~100字节的应用而言。假设採用默认的开启Nagle算法。我在发送端,固定每帧数据85个,间隔100ms发送一次,我在接受端(堵塞方式使用)接受的数据是43 138交替出现,可能就是这个算法的时间阈值问题,假设关闭Nagle算法,在接收端就可以保证数据每次接收到的都是85帧。
Nagle算法适用于小包、高延迟的场合,而对于要求交互速度的b/s或c/s就不合适了。
socket在建立的时候。默认都是使用Nagle算法的,这会致使交互速度严重降低,因此需要setsockopt函数来设置TCP_NODELAY为1.只是取消了Nagle算法,就会致使TCP碎片增多。效率可能会减小。
关闭nagle算法,以避免影响性能。因为控制时控制端要发送很是多数据量很是小的数据包,需要当即发送。
const char chOpt = 1;
int nErr = setsockopt(pContext->m_Socket, IPPROTO_TCP, TCP_NODELAY, &chOpt, sizeof(char));
if (nErr == -1)
{
TRACE(_T("setsockopt() error\n"),WSAGetLastError());
return;
}
setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on)); //set TCP_CORK
TCP传输小数据包效率问题
摘要:当使用TCP传输小型数据包时。程序的设计是至关重要的。假设在设计方案中不正确TCP数据包的
延迟应答,Nagle算法。Winsock缓冲做用引发重视,将会严重影响程序的性能。这篇文章讨论了这些
问题,列举了两个案例。给出了一些传输小数据包的优化设计方案。
背景:当Microsoft TCP栈接收到一个数据包时,会启动一个200毫秒的计时器。当ACK确认数据包
发出以后,计时器会复位,接收到下一个数据包时。会再次启动200毫秒的计时器。为了提高应用程序
在内部网和Internet上的传输性能,Microsoft TCP栈使用了如下的策略来决定在接收到数据包后
何时发送ACK确认数据包:
一、假设在200毫秒的计时器超时以前。接收到下一个数据包。则立刻发送ACK确认数据包。
二、假设当前刚好有数据包需要发给ACK确认信息的接收端,则把ACK确认信息附带在数据包上立刻发送。
三、当计时器超时,ACK确认信息立刻发送。
为了不小数据包拥塞网络。Microsoft TCP栈默认启用了Nagle算法,这个算法能够将应用程序屡次
调用Send发送的数据拼接起来,当收到前一个数据包的ACK确认信息时,一块儿发送出去。如下是Nagle
算法的例外状况:
一、假设Microsoft TCP栈拼接起来的数据包超过了MTU值,这个数据会立刻发送,而不等待前一个数据
包的ACK确认信息。在以太网中,TCP的MTU(Maximum Transmission Unit)值是1460字节。
二、假设设置了TCP_NODELAY选项。就会禁用Nagle算法。应用程序调用Send发送的数据包会立刻被
投递到网络,而没有延迟。
为了在应用层优化性能,Winsock把应用程序调用Send发送的数据从应用程序的缓冲区拷贝到Winsock
内核缓冲区。Microsoft TCP栈利用相似Nagle算法的方法,决定何时才实际地把数据投递到网络。
内核缓冲区的默认大小是8K,使用SO_SNDBUF选项,可以改变Winsock内核缓冲区的大小。假设有必要的话。
Winsock能缓冲大于SO_SNDBUF缓冲区大小的数据。在绝大多数状况下,应用程序完毕Send调用只代表数据
被拷贝到了Winsock内核缓冲区,并不能说明数据就实际地被投递到了网络上。
惟一一种例外的状况是:
经过设置SO_SNDBUT为0禁用了Winsock内核缓冲区。
Winsock使用如下的规则来向应用程序代表一个Send调用的完毕:
一、假设socket仍然在SO_SNDBUF限额内,Winsock复制应用程序要发送的数据到内核缓冲区。完毕Send调用。
二、假设Socket超过了SO_SNDBUF限额并且先前仅仅有一个被缓冲的发送数据在内核缓冲区,Winsock复制要发送
的数据到内核缓冲区,完毕Send调用。
三、假设Socket超过了SO_SNDBUF限额并且内核缓冲区有不只仅一个被缓冲的发送数据,Winsock复制要发送的数据
到内核缓冲区,而后投递数据到网络。直到Socket降到SO_SNDBUF限额内或者仅仅剩余一个要发送的数据,才
完毕Send调用。
案例1
一个Winsock TCPclient需要发送10000个记录到Winsock TCP服务端,保存到数据库。记录大小从20字节到100
字节不等。
对于简单的应用程序逻辑,可能的设计方案例如如下:
一、client以堵塞方式发送。服务端以堵塞方式接收。
二、client设置SO_SNDBUF为0。禁用Nagle算法,让每个数据包单独的发送。
三、服务端在一个循环中调用Recv接收数据包。给Recv传递200字节的缓冲区以便让每个记录在一次Recv调用中
被获取到。
性能:
在測试中发现。client每秒仅仅能发送5条数据到服务段。总共10000条记录,976K字节左右。用了半个多小时
才全部传到server。
分析:
因为client没有设置TCP_NODELAY选项,Nagle算法强制TCP栈在发送数据包以前等待前一个数据包的ACK确认
信息。
然而,client设置SO_SNDBUF为0,禁用了内核缓冲区。所以,10000个Send调用仅仅能一个数据包一个数据
包的发送和确认,由于下列缘由,每个ACK确认信息被延迟200毫秒:
一、当server获取到一个数据包,启动一个200毫秒的计时器。
二、服务端不需要向client发送不论什么数据,因此。ACK确认信息不能被发回的数据包顺路携带。
三、client在没有收到前一个数据包的确认信息前,不能发送数据包。
四、服务端的计时器超时后。ACK确认信息被发送到client。
怎样提升性能:
在这个设计中存在两个问题。第一,存在延时问题。client需要能够在200毫秒内发送两个数据包到服务端。
因为client默认状况下使用Nagle算法,应该使用默认的内核缓冲区,不该该设置SO_SNDBUF为0。一旦TCP
栈拼接起来的数据包超过MTU值。这个数据包会立刻被发送,不用等待前一个ACK确认信息。第二,这个设计
方案对每一个如此小的的数据包都调用一次Send。
发送这么小的数据包是不很是有效率的。在这样的状况下。应该
把每个记录补充到100字节并且每次调用Send发送80个记录。为了让服务端知道一次总共发送了多少个记录,
client可以在记录前面带一个头信息。
案例二:
一个Winsock TCPclient程序打开两个链接和一个提供股票报价服务的Winsock TCP服务端通讯。
第一个链接
做为命令通道用来传输股票编号到服务端。第二个链接做为数据通道用来接收股票报价。两个链接被创建后,
client经过命令通道发送股票编号到服务端。而后在数据通道上等待返回的股票报价信息。
client在接收到第一
个股票报价信息后发送下一个股票编号请求到服务端。client和服务端都没有设置SO_SNDBUF和TCP_NODELAY
选项。
性能:
測试中发现,client每秒仅仅能获取到5条报价信息。
分析:
这个设计方案一次仅仅赞成获取一条股票信息。
第一个股票编号信息经过命令通道发送到服务端。立刻接收到
服务端经过数据通道返回的股票报价信息。而后。client立刻发送第二条请求信息。send调用立刻返回,
发送的数据被拷贝到内核缓冲区。然而,TCP栈不能立刻投递这个数据包到网络。因为没有收到前一个数据包的
ACK确认信息。200毫秒后。服务端的计时器超时,第一个请求数据包的ACK确认信息被发送回client。client
的第二个请求包才被投递到网络。第二个请求的报价信息立刻从数据通道返回到client,因为此时。client的
计时器已经超时,第一个报价信息的ACK确认信息已经被发送到服务端。
这个过程循环发生。
怎样提升性能:
在这里。两个链接的设计是没有必要的。
假设使用一个链接来请求和接收报价信息,股票请求的ACK确认信息会
被返回的报价信息立刻顺路携带回来。
要进一步的提升性能,client应该一次调用Send发送多个股票请求,服务端
一次返回多个报价信息。假设由于某些特殊缘由必须要使用两个单向的链接,client和服务端都应该设置TCP_NODELAY
选项,让小数据包立刻发送而不用等待前一个数据包的ACK确认信息。
提升性能的建议:
上面两个案例说明了一些最坏的状况。当设计一个方案解决大量的小数据包发送和接收时,应该遵循下面的建议:
一、假设数据片断不需要紧急传输的话。应用程序应该将他们拼接成更大的数据块,再调用Send。因为发送缓冲区
很是可能被拷贝到内核缓冲区,因此缓冲区不该该太大,一般比8K小一点点是很是有效率的。
仅仅要Winsock内核缓冲区
获得一个大于MTU值的数据块,就会发送若干个数据包。剩下最后一个数据包。
发送方除了最后一个数据包,都不会
被200毫秒的计时器触发。
二、假设可能的话,避免单向的Socket数据流接连。
三、不要设置SO_SNDBUF为0。除非想确保数据包在调用Send完毕以后立刻被投递到网络。其实,8K的缓冲区适合大多数
状况。不需要又一次改变。除非新设置的缓冲区通过測试的确比默认大小更高效。
四、假设传输数据不用保证可靠性,使用UDP。