?!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
摘要Q当使用TCP传输型数据包时Q程序的设计是相当重要的。如果在设计Ҏ中不对TCP数据包的延迟应答QNagle法QWinsock~冲作用引v重视Q将会严重媄响程序的性能。这文章讨Zq些问题Q列举了两个案例Q给Z一些传输小数据包的优化设计Ҏ?/span>
背景Q当Microsoft TCP栈接收到一个数据包Ӟ会启动一?00毫秒的计时器。当ACK认数据包发Z后,计时器会复位Q接收到下一个数据包Ӟ会再ơ启?00毫秒的计时器。ؓ了提升应用程序在内部|和Internet上的传输性能QMicrosoft TCP栈用了下面的策略来军_在接收到数据包后什么时候发送ACK认数据包:
1、如果在200毫秒的计时器时之前Q接收到下一个数据包Q则立即发送ACK认数据包?/span>
2、如果当前恰好有数据包需要发lACK认信息的接收端Q则把ACK认信息附带在数据包上立卛_送?/span>
3、当计时器超ӞACK认信息立即发送?/span>
Z避免数据包拥塞|络QMicrosoft TCP栈默认启用了Nagle法Q这个算法能够将应用E序多次调用Send发送的数据拼接hQ当收到前一个数据包的ACK认信息Ӟ一起发送出厅R下面是Nagle法的例外情况:
1、如果Microsoft TCP栈拼接v来的数据包超q了MTU|q个数据会立卛_送,而不{待前一个数据包的ACK认信息。在以太|中QTCP的MTU(Maximum Transmission Unit)值是1460字节?/span>
2、如果设|了TCP_NODELAY选项Q就会禁用Nagle法Q应用程序调用Send发送的数据包会立即被投递到|络Q而没有gq?/span>
Z在应用层优化性能QWinsock把应用程序调用Send发送的数据从应用程序的~冲区复制到Winsock内核~冲区。Microsoft TCP栈利用类似Nagle法的方法,军_什么时候才实际地把数据投递到|络。内核缓冲区的默认大是8KQ用SO_SNDBUF选项Q可以改变Winsock内核~冲区的大小。如果有必要的话QWinsock能缓冲大于SO_SNDBUF~冲区大的数据。在l大多数情况下,应用E序完成Send调用仅仅表明数据被复制到了Winsock内核~冲区,q不能说明数据就实际地被投递到了网l上。唯一一U例外的情况是:通过讄SO_SNDBUT?用了Winsock内核~冲区?/span>
Winsock使用下面的规则来向应用程序表明一个Send调用的完成:
1、如果socket仍然在SO_SNDBUF限额内,Winsock复制应用E序要发送的数据到内核缓冲区Q完成Send调用?/span>
2、如果Socket过了SO_SNDBUF限额q且先前只有一个被~冲的发送数据在内核~冲区,Winsock复制要发送的数据到内核缓冲区Q完成Send调用?/span>
3、如果Socket过了SO_SNDBUF限额q且内核~冲区有不只一个被~冲的发送数据,Winsock复制要发送的数据到内核缓冲区Q然后投递数据到|络Q直到Socket降到SO_SNDBUF限额内或者只剩余一个要发送的数据Q才完成Send调用?/span>
案例1
一个Winsock TCP客户端需要发?0000个记录到Winsock TCP服务端,保存到数据库。记录大从20字节?00字节不等。对于简单的应用E序逻辑Q可能的设计Ҏ如下Q?/span>
1、客L以阻塞方式发送,服务端以d方式接收?/span>
2、客L讄SO_SNDBUF?Q禁用Nagle法Q让每个数据包单独的发送?/span>
3、服务端在一个@环中调用Recv接收数据包。给Recv传?00字节的缓冲区以便让每个记录在一ơRecv调用中被获取到?/span>
性能Q?/span>
在测试中发现Q客L每秒只能发?条数据到服务D,d10000条记录,976K字节左右Q用了半个多时才全部传到服务器?/span>
分析Q?/span>
因ؓ客户端没有设|TCP_NODELAY选项QNagle法强制TCP栈在发送数据包之前{待前一个数据包的ACK认信息。然而,客户端设|SO_SNDBUF?Q禁用了内核~冲区。因此,10000个Send调用只能一个数据包一个数据包的发送和认Q由于下列原因,每个ACK认信息被gq?00毫秒Q?/span>
1、当服务器获取到一个数据包Q启动一?00毫秒的计时器?/span>
2、服务端不需要向客户端发送Q何数据,所以,ACK认信息不能被发回的数据包顺路携带?/span>
3、客L在没有收到前一个数据包的确认信息前Q不能发送数据包?/span>
4、服务端的计时器时后,ACK认信息被发送到客户端?/span>
如何提高性能Q?/span>
在这个设计中存在两个问题。第一Q存在g旉题。客L需要能够在200毫秒内发送两个数据包到服务端。因为客L默认情况下用Nagle法Q应该用默认的内核~冲区,不应该SO_SNDBUF?。一旦TCP栈拼接v来的数据包超qMTU|q个数据包会立即被发送,不用{待前一个ACK认信息。第二,q个设计ҎҎ一个如此小的的数据包都调用一ơSend。发送这么小的数据包是不很有效率的。在q种情况下,应该把每个记录补充到100字节q且每次调用Send发?0个记录。ؓ了让服务端知道一ơd发送了多少个记录,客户端可以在记录前面带一个头信息?/span>
案例二:
一个Winsock TCP客户端程序打开两个q接和一个提供股报h务的Winsock TCP服务端通信。第一个连接作为命令通道用来传输股票~号到服务端。第二个q接作ؓ数据通道用来接收股票报h。两个连接被建立后,客户端通过命o通道发送股编号到服务端,然后在数据通道上等待返回的股票报h信息。客L在接收到W一个股报价信息后发送下一个股编可求到服务端。客L和服务端都没有设|SO_SNDBUF和TCP_NODELAY选项?/span>
性能Q?/span>
试中发玎ͼ客户端每U只能获取到5条报价信息?/span>
分析Q?/span>
q个设计Ҏ一ơ只允许获取一条股信息。第一个股编号信息通过命o通道发送到服务端,立即接收到服务端通过数据通道q回的股报价信息。然后,客户端立卛_送第二条h信息Qsend调用立即q回Q发送的数据被复制到内核~冲区。然而,TCP栈不能立x递这个数据包到网l,因ؓ没有收到前一个数据包的ACK认信息?00毫秒后,服务端的计时器超ӞW一个请求数据包的ACK认信息被发送回客户端,客户端的W二个请求包才被投递到|络。第二个h的报价信息立即从数据通道q回到客LQ因为此Ӟ客户端的计时器已l超ӞW一个报价信息的ACK认信息已经被发送到服务端。这个过E@环发生?/span>
如何提高性能Q?/span>
在这里,两个q接的设计是没有必要的。如果用一个连接来h和接收报价信息,股票h的ACK认信息会被q回的报价信息立即顺路携带回来。要q一步的提高性能Q客L应该一ơ调用Send发送多个股请求,服务端一ơ返回多个报价信息。如果由于某些特D原因必要使用两个单向的连接,客户端和服务端都应该讄TCP_NODELAY选项Q让数据包立即发送而不用等待前一个数据包的ACK认信息?/span>
提高性能的徏议:
上面两个案例说明了一些最坏的情况。当设计一个方案解军_量的数据包发送和接收Ӟ应该遵@以下的徏议:
1、如果数据片D不需要紧急传输的话,应用E序应该他们拼接成更大的数据块Q再调用Send。因为发送缓冲区很可能被复制到内核缓冲区Q所以缓冲区不应该太大,通常?K一点点是很有效率的。只要Winsock内核~冲区得C个大于MTU值的数据块,׃发送若q个数据包,剩下最后一个数据包。发送方除了最后一个数据包Q都不会?00毫秒的计时器触发?/span>
2、如果可能的话,避免单向的Socket数据接q?/span>
3、不要设|SO_SNDBUF?Q除非想保数据包在调用Send完成之后立即被投递到|络。事实上Q?K的缓冲区适合大多数情况,不需要重新改变,除非新设|的~冲区经q测试的比默认大小更高效?/span>