网站运营推广这么做,网站设计 下拉式菜单怎么做,建立自信,衡水网站建设网络公司TCP/IP协议涉及到四层#xff0c;从底层到上层异常为#xff1a;链路层#xff0c;网络层#xff0c;传输层#xff0c;应用层。 其中以太网#xff08;Ethernet#xff09;的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或UDP中的数据从底层到上层异常为链路层网络层传输层应用层。 其中以太网Ethernet的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或UDP中的数据Data)在应用层 它们的关系是 数据帧IP包TCP或UDP包Data --------------------------------------------------------------------------------- 在应用程序中我们用到的Data的长度最大是多少直接取决于底层的限制。 我们从下到上分析一下 1.在链路层由以太网的物理特性决定了数据帧的长度为4618150018其中的18是数据帧的头和尾也就是说数据帧的内容最大为1500不包括帧头和帧尾即MTUMaximum Transmission Unit为1500 2.在网络层因为IP包的首部要占用20字节所以这的MTU为1500201480 3.在传输层对于UDP包的首部要占用8字节所以这的MTU为148081472 所以在应用层你的Data最大长度为1472。 当我们的UDP包中的数据多于MTU(1472)时发送方的IP层需要分片fragmentation进行传输而在接收方IP层则需要进行数据报重组由于UDP是不可靠的传输协议如果分片丢失导致重组失败将导致UDP数据包被丢弃。 从上面的分析来看在普通的局域网环境下UDP的数据最大为1472字节最好避免分片重组。 但在网络编程中Internet中的路由器可能有设置成不同的值小于默认值Internet上的标准MTU值为576所以Internet的UDP编程时数据长度最好在576208548字节以内。 --------------------------------------------------------------------------------- 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。 原理ping程序使用ICMP报文ICMP报文首部占8字节IP数据报首部占20字节因此在数据大小基础上加上28字节为MTU值。---------------------------------------------------------------------------------
IP数据包的最大长度是64K字节(65535)因为在IP包头中用2个字节描述报文长度2个字节所能表达的最大数字就是65535。 由于IP协议提供为上层协议分割和重组报文的功能因此传输层协议的数据包长度原则上来说没有限制。实际上限制还是有的因为IP包的标识字段终究不可能无限长按照IPv4好像上限应该是4G(64K*64K)。依靠这种机制TCP包头中就没有“包长度”字段而完全依靠IP层去处理分帧。这就是为什么TCP常常被称作一种“流协议”的原因开发者在使用TCP服务的时候不必去关心数据包的大小只需讲SOCKET看作一条数据流的入口往里面放数据就是了TCP协议本身会进行拥塞/流量控制。 UDP则与TCP不同UDP包头内有总长度字段同样为两个字节因此UDP数据包的总长度被限制为65535这样恰好可以放进一个IP包内使得UDP/IP协议栈的实现非常简单和高效。65535再减去UDP头本身所占据的8个字节UDP服务中的最大有效载荷长度仅为65527。这个值也就是你在调用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\nB块总是在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确认数据包 1、如果在200毫秒的计时器超时之前接收到下一个数据包则立即发送ACK确认数据包。 2、如果当前恰好有数据包需要发给ACK确认信息的接收端则把ACK确认信息附带在数据包上立即发送。 3、当计时器超时ACK确认信息立即发送。 为了避免小数据包拥塞网络Microsoft TCP栈默认启用了Nagle算法这个算法能够将应用程序多次 调用Send发送的数据拼接起来当收到前一个数据包的ACK确认信息时一起发送出去。下面是Nagle 算法的例外情况 1、如果Microsoft TCP栈拼接起来的数据包超过了MTU值这个数据会立即发送而不等待前一个数据 包的ACK确认信息。在以太网中TCP的MTU(Maximum Transmission Unit)值是1460字节。 2、如果设置了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调用的完成 1、如果socket仍然在SO_SNDBUF限额内Winsock复制应用程序要发送的数据到内核缓冲区完成Send调用。 2、如果Socket超过了SO_SNDBUF限额并且先前只有一个被缓冲的发送数据在内核缓冲区Winsock复制要发送 的数据到内核缓冲区完成Send调用。 3、如果Socket超过了SO_SNDBUF限额并且内核缓冲区有不只一个被缓冲的发送数据Winsock复制要发送的数据 到内核缓冲区然后投递数据到网络直到Socket降到SO_SNDBUF限额内或者只剩余一个要发送的数据才 完成Send调用。
案例1 一个Winsock TCP客户端需要发送10000个记录到Winsock TCP服务端保存到数据库。记录大小从20字节到100 字节不等。对于简单的应用程序逻辑可能的设计方案如下 1、客户端以阻塞方式发送服务端以阻塞方式接收。 2、客户端设置SO_SNDBUF为0禁用Nagle算法让每个数据包单独的发送。 3、服务端在一个循环中调用Recv接收数据包。给Recv传递200字节的缓冲区以便让每个记录在一次Recv调用中 被获取到。
性能 在测试中发现客户端每秒只能发送5条数据到服务段总共10000条记录976K字节左右用了半个多小时 才全部传到服务器。
分析 因为客户端没有设置TCP_NODELAY选项Nagle算法强制TCP栈在发送数据包之前等待前一个数据包的ACK确认 信息。然而客户端设置SO_SNDBUF为0禁用了内核缓冲区。因此10000个Send调用只能一个数据包一个数据 包的发送和确认由于下列原因每个ACK确认信息被延迟200毫秒 1、当服务器获取到一个数据包启动一个200毫秒的计时器。 2、服务端不需要向客户端发送任何数据所以ACK确认信息不能被发回的数据包顺路携带。 3、客户端在没有收到前一个数据包的确认信息前不能发送数据包。 4、服务端的计时器超时后ACK确认信息被发送到客户端。
如何提高性能 在这个设计中存在两个问题。第一存在延时问题。客户端需要能够在200毫秒内发送两个数据包到服务端。 因为客户端默认情况下使用Nagle算法应该使用默认的内核缓冲区不应该设置SO_SNDBUF为0。一旦TCP 栈拼接起来的数据包超过MTU值这个数据包会立即被发送不用等待前一个ACK确认信息。第二这个设计 方案对每一个如此小的的数据包都调用一次Send。发送这么小的数据包是不很有效率的。在这种情况下应该 把每个记录补充到100字节并且每次调用Send发送80个记录。为了让服务端知道一次总共发送了多少个记录 客户端可以在记录前面带一个头信息。
案例二 一个Winsock TCP客户端程序打开两个连接和一个提供股票报价服务的Winsock TCP服务端通信。第一个连接 作为命令通道用来传输股票编号到服务端。第二个连接作为数据通道用来接收股票报价。两个连接被建立后 客户端通过命令通道发送股票编号到服务端然后在数据通道上等待返回的股票报价信息。客户端在接收到第一 个股票报价信息后发送下一个股票编号请求到服务端。客户端和服务端都没有设置SO_SNDBUF和TCP_NODELAY 选项。
性能 测试中发现客户端每秒只能获取到5条报价信息。
分析
这个设计方案一次只允许获取一条股票信息。第一个股票编号信息通过命令通道发送到服务端立即接收到 服务端通过数据通道返回的股票报价信息。然后客户端立即发送第二条请求信息send调用立即返回 发送的数据被复制到内核缓冲区。然而TCP栈不能立即投递这个数据包到网络因为没有收到前一个数据包的 ACK确认信息。200毫秒后服务端的计时器超时第一个请求数据包的ACK确认信息被发送回客户端客户端 的第二个请求包才被投递到网络。第二个请求的报价信息立即从数据通道返回到客户端因为此时客户端的 计时器已经超时第一个报价信息的ACK确认信息已经被发送到服务端。这个过程循环发生。
如何提高性能 在这里两个连接的设计是没有必要的。如果使用一个连接来请求和接收报价信息股票请求的ACK确认信息会 被返回的报价信息立即顺路携带回来。要进一步的提高性能客户端应该一次调用Send发送多个股票请求服务端 一次返回多个报价信息。如果由于某些特殊原因必须要使用两个单向的连接客户端和服务端都应该设置TCP_NODELAY 选项让小数据包立即发送而不用等待前一个数据包的ACK确认信息。
提高性能的建议 上面两个案例说明了一些最坏的情况。当设计一个方案解决大量的小数据包发送和接收时应该遵循以下的建议 1、如果数据片段不需要紧急传输的话应用程序应该将他们拼接成更大的数据块再调用Send。因为发送缓冲区 很可能被复制到内核缓冲区所以缓冲区不应该太大通常比8K小一点点是很有效率的。只要Winsock内核缓冲区 得到一个大于MTU值的数据块就会发送若干个数据包剩下最后一个数据包。发送方除了最后一个数据包都不会 被200毫秒的计时器触发。 2、如果可能的话避免单向的Socket数据流接连。 3、不要设置SO_SNDBUF为0除非想确保数据包在调用Send完成之后立即被投递到网络。事实上8K的缓冲区适合大多数 情况不需要重新改变除非新设置的缓冲区经过测试的确比默认大小更高效。 4、如果数据传输不用保证可靠性使用UDP。