第四章 Winsock高级问题

来源:互联网 发布:mac怎么发送文件夹 编辑:程序博客网 时间:2024/06/02 02:24

 

Winsock编程常见问答

第四章 Winsock高级问题

原文:http://tangentsoft.net/wskfaq/advanced.html 

译者:jovia  

时间:201042日

 

4.1 Winsock支持原始套接字吗?

是的,Winsock支持原始套接字,但存在一些限制。

例如:SOCKET sd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

       这表示我们希望建立一个基于IP协议(AF_INET) 的原始套接字,该套接字使用ICMP相对应的在册IP协议号----这里,Winsock头文件已经很贴心地提供了IPPROTO_ICMP这个常量。 尽管Winsock头文件定义了很多IPPROTO_*这样的常量,但实际存在的基于IP的协议数目远多于此(详见IP协议号列表。你可从IP协议号列表中选取任意一个值传递给socket()函数,这将允许你的程序实现该协议(这里有一些限制,详见下文)。例如,如果想在用户空间实现SCTP这个协议,那么可传递132这个值。你甚至可利用这个特点来实现一些新的基于IP的上层协议。截止到笔者写本文时,协议号141-252还没有被分配;建议你使用253254来做测试工作,因为它们是专门为做测试而预留的协议号。

       只有在别无它法时,你才应该使用原始套接字来实现自己的目的。

原始套接字存在如下几个问题:

       1.必须在主机上拥有管理员的权限,你的程序才能使用原始套接字。

       2.同使用协议的传输数据接口(Transport Data Interface)的内核协议驱动相比,原始套接字要慢许多。TDI的工作机制已被文档化了,它可跨越内核/用户空间这一壁垒,同Winsock紧密协作。

      一度,关于编写驱动的最大争议就是“必须拥有管理员的权限,你才能安装驱动”。但自从这一限制也被添加到原始套接字上之后,就有了这样的反面论据:至少,非特权用户可通过Winsock来使用TDI协议驱动。(在上文中,我给出了SCTP的例子,你可仅仅为了好玩而在用户空间上实现它,但若想做成产品,你就很可能需要购买一个现成的内核驱动了。)

       3.有一些事情是原始套接字无法做到的,这要么是因为微软在Windows操作系统中没有添加该功能,要么是出于安全考虑而特意禁止了该功能。

       4.尽管你可以使用原始套接字来抓包capture packets,但这存在很多问题。请参考相关问题的FAQ条目和推荐的替代方案。

       Windows NT 4 仅仅支持原始ICMP协议和原始IGMP协议,这主要是为了让程序以标准方式发送“ping”数据包(send “ping” packets。我没看出原始IGMP套接字有何用处,因为Winsock已经为组播组管理提供了许多应用程序接口(API,而这些API应该就够用了。

      相对于Windows NT 4而言,Windows 2000极大地增强了对原始套接字的支持。以下操作系统提供了与Windows 2000相同的原始套接字功能:

Windows XP (original release)

Windows XP SP1

Windows Server 2003

Windows Server 2008 (original release)

       微软在Windows XP SP2开始的个人版操作系统及Windows Server 2008 R2开始的服务器版操作系统上,对原始套接字增加了一些限制:

1原始TCP包已被完全禁用了。

2对原始UDP包的限制没那么严,但你再也不能伪造源地址了。

3再也不允许你把原始套接字绑定到特定的网卡接口上了。这在我看来有点奇怪,这倒更像是“取消限制”了,因为这意味着原始套接字始终从所有网卡接口上接收那些含有请求的IP协议号的数据包。

其中,前两条是为了阻止某些蠕虫病毒。

大多数时候,使用原始套接字只需在IP层之上进行开发,让协议来提供IP头并填充IP头的所有字段。但是,如果你需要修改IP头字段中的一个或几个,那就先看看setsockopt()函数的文档说明吧。你会发现操作系统已经为这种情况准备了可选选项。例如,可用IP_TTL选项来设置TTL字段的默认值。如果没有对应的选项存在,而你又正在高于Windows NT 4版本的现代(modern操作系统上运行程序,那么你可通过IP_HDRINCL这个套接字选项来实现想要的功能。这个IP_HDRINCL选项告诉协议,你传递给它的数据将包含IP头以及上层协议的头。

 

4.2 - 如何使用Winsock在局域网上抓包?

      Windows 2000及之后版本的Windows操作系统上,你可通过向WSAIoctl()函数的第二个参数传递SIO_RCVALL来实现局域网抓包。

这种方法存在如下几个问题:

1. 必须有管理员的权限。

2. 在从Windows 2000 Windows Server 2008 (original release)版本的windows操作系统上,这种方法只能让你看到进来的(incoming)数据包。直到Windows Server 2008 R2Windows 7,这种方法才允许你看到从本机发送出去的数据包。

3. 这种方法很容易被TCP负载分流引擎(TCP Offload Engines)等拦截掉。下面推荐的几个替代方案工作在更低的层次上,所以它们只需简单地要求网卡接口运行在“混杂”模式上即可,这使得网卡会传递所有数据而不进行任何处理!SIO_RCVALL的当前实现不是这样做的。有时在控制面板上会有一个驱动选项允许你对网卡工作模式进行控制,但这比较麻烦。

      大多通用型的桌面操作系统都提供了让内核为你做某些过滤工作的方法,这与SIO_RCVALL选项不同。这是你所需要的,因为你的程序很可能只对某些数据包感兴趣,所以你必须过滤掉你不感兴趣的数据包。在千兆网速下,数据包过滤将使得CPU的占用率出奇得高。你可能无法那么快速地进行处理,最终导致内核的缓冲空间被耗尽,内核被迫丢弃数据包。把过滤工作至少部分地转移到在内核中去才是可行的方案,因为这省去了过滤一个数据包时的内核/用户空间上下文转换。

       一个更好的方案是跳过Winsock,直接同传输数据接口(TDI) 层或网络设备接口规范(NDIS)层打交道。TDI层恰好在系统的NDIS(网络驱动)层之上。

      你可能不需要自己写代码。Windows版本的Wireshark软件(一款著名的开源性质的跨平台嗅探器sniffer)所使用的抓包驱动(就是所谓的WinPcap)是免费提供的。如果你安装过Wireshark,你可能还记得WinPcap安装程序出现并运行过。

      WinPcap可解决上面的所有问题。唯一不足之处是,你必须有管理员权限才能安装WinPcap,“允许非特权用户使用抓包服务”是安装时的一个可选选项。如果你是已安装了WinPcap驱动的系统上的非特权用户,那么系统管理员在安装WinPcap时很可能禁用了“允许非特权用户使用抓包服务”这一功能。

      如果你确实需要自己编写抓包驱动,那么最好参考一些助手库以简化你的工作。在写作本文时,我所知道的有Komodia TCP/IP LibraryLibnetNT WinDis32.

      PCAUSA---makers的作者---也维护着几个常见问答列表(several FAQs,主要讨论各种访问底层网络协议的方法。这些问答还会链接到一些示例代码片断,其中大部分出自微软的各种DDK。

 

4.3 - 如何改变一个数据包的内容?

如果你只需改变一个流出数据包的TCPIP头,那么请查看MSDN库(MSDN Library)中的setsockopt(), ioctlsocket() WSAIoctl()。你可能会发现有针对该需求的选项。例如,设置IP_TTL一套接字选项后,你就能够修改IP头的TTL字段了。

如果这些方法不能暴露出你想修改的字段,那么你可利用原始套接字来构建自己的数据包头。注意:较新版本的windows操作系统已经添加了一些限制,禁止了某些类型的修改操作,这通常是出于安全考虑的。

如果你需要更彻底的控制,那么就必须深入到Winsock API层之下去:

       一种选择是编写分层服务提供者(Layered Service Provider)程序。LSP可把自己插入到协议中去,因此它可随意地查看或修改数据。例如,典型的Web过滤程序就是使用LSP来实现的。LSP也是Winsock的功能之一,但本文只关注Winsock APILSPWinsock的另外一个接口----服务提供者接口(SPI)----打交道。我所知道的最好的Winsock SPI参考资料就是MSDN库了。

       另一种选择就是编写一个同TDI层或NDIS层打交道的驱动程序,在PCAUSA的常见问答列表(PCAUSAs FAQs)上有更多相关信息。

       最后要说的是,在一个更易于访问到底层数据包细节的平台上,不要太仓促地排除“自己编写程序”这一选择。众多的Unix爱好者(包括Linux)为底层网络I/O提供了强大的支持。这就是基于Linux BSD Unix平台的网络应用程序多于基于Windows平台的网络应用程序的原因之一。基于此类平台的原始网络编程信息,请参考Thamer Al-Herbish的原始IP网络编程常见问答列表(Raw IP Networking FAQ

 

4.4 - 如何“ping 其它主机?

官方的办法是使用IPPROTO_ICMP类型的原始套接字,现代(modern)版本的Windows操作系统和几个较老的操作系统都支持这一方法。[C++ example]

       另一种方法是使用ICMP.DLL----Windows操作系统上的一个较古老的组成部分,微软声称(claims)要将其删除。据说该DLL至少从Windows XP时代开始就存在了,在Windows 7中仍然存在。之所以毫无疑问地被保留了下来,是因为ICMP.DLL与原始套接字不同,它允许你在没有管理员权限的情况下发送ping数据包。但有一点,原始套接字确实优于ICMP.DLL:原始套接字要求你从头开始构建原始ICMP数据包,所以你能够完全控制该包的内容。ICMP.DLL更简单易用一些,但功能没有那么强大。[C++ example]

许多程序误用了ping。当然,ping是很有用的,但如果你发现自己经常诉诸于使用ping数据包,那么这就是程序或协议糟糕的一个迹象。滥用ping的最常见场景就是程序使用ping来探测断开的连接(detect dropped connections.)。参考相关FAQ条目以获取此问题的解决方法。

 

4.5 - 可以创建映射到DLL而不是应用程序的套接字吗?

      Windows上,一个DLL的数据实际上是属于装载这个DLL的应用程序的。如果你需要让某个DLL拥有单一实例的套接字,而不管有多少进程装载了这个DLL,那么你需要创建一个全权代表这个DLL的“辅助进程”来进行所有的Winsock操作。当然,你还需要在这个DLL和这个辅助进程之间创建某种跨进程通信的通道。

      注意:只有当你想通过一个DLL来实现在多个进程之间共享socket时,这个问题才至关重要。如果你只有一个进程使用这个DLL,或者每个进程并不需要考虑其它进程是否使用了这个DLL时,你就不必关系这个问题。

 

4.6 - 如何访问{路由,ARP,接口,…}表?

使用WindowsSNMP API它允许你访问Windows网络子系统的许多“隐藏”部分,包括:

网络接口列表,

路由和ARP表,

已连接的网络套接字列表,

以太网卡的硬件地址,

等等

FAQ系列的例(examples中有一个(One用的就是SNMP API

 

4.7 -  如何获取本地以太网适配器的MAC地址

    本FAQ系列包含了示例代码:提供了两个较简单(hackish)的方法和一个复杂但可靠的方法。

This FAQ has example code for two hackish methods and one complex but reliable method.

       第一种方法(first method)是通过调用NetBIOS API取得适配器的地址。在没有NetBIOS的系统上这种方法会失效或者会返回错误的结果。

       第二种方法(second method )依赖于RPC/OLE API的一个特性。这个特性已被文档化了,但是不能保证一定可以完成我们的任务,事实上,在许多环境下都会失败(详见示例程序的说明)。所以,我不得不建议你忽略这种方法。

       第三种方法(third method)是使用半文档化的SNMP API来获取MAC地址。这种方法好像在任何时候总能工作,但比上两种方法复杂得多。

       还有一种方法,目前我还没有具体的例子:IP 助手API中有一个GetIfTable()函数可返回一个含有MAC地址的表,其中还包含了许多其它重要信息。这种方法可在所有现代(modern版本的Windows和部分较老的操作系统上工作。据说,你必须使用LoadLibrary()函数从iphlpapi.dll中找到这个函数的地址,因为这个函数没有被输出,所以不能直接链接。这样也好,因为当你的程序在不带有该函数的windows版本上运行时,隐式链接到iphlpapi.dll允许程序从容失败。

       PCAUSANDIS常见问答列表中还有一些可能对你也有用的底层方法(lower-level methods)。

 

4.8 -可以并发地打开多少个套接字?

任何现代(modern版本的Windows操作系统都没有对连接或套接字的数目做固定限制。所谓的上限值取决于你采用的I/O策略(I/O strategy),系统内存的大小和程序所采用的网络模型:

I/O策略因素:正如上面链接的那篇文章所说,Winsock可使用的I/O策略有许多种。它们各有千秋,其中要比较的一点就是在大量并发连接的情况下该策略的性能如何。如果你必须处理几千个连接,那么你应该使用重叠I/O,因为这是唯一能保证你获取如此之高的并发连接数的I/O策略。其它的策略(如异步通知, select(), thread-per-socket... )将在网络协议本身耗尽资源之前就遭遇某些性能极限。有的占有CPU资源太多,有的需要大量上下文切换,还有一些使用了效率低下的通知机制。

内存因素:根据微软的资料,所有现代版本的windows都从非分页内存池(即,不能被虚拟内存子系统交换到页面文件中去的那部分内存)中分配socket资源。非分页内存池的大小取决于系统物理内存的大小,肯定是个固定的值。在Intel x86主机上,非分页内存池的大小在达到物理内存尺寸的1/8后就停止增长了。在Windows NT 4.0上非分页内存池的最大值固定为128M字节,在Windows 2000上为256M字节,这也分别是1 2 GB RAM中非分页内存池的最大大小。(我不清楚在更新版本的Windows系统中这一上限是否增大了,或者64位的CPU是否有一些特殊之处。如果你知道,请发电子邮件告诉我。)

 “繁忙”因素:与一个套接字相关联的内存数据量是变化的,这取决于该套接字是被如何使用的,但最小大小是2K字节。重叠I/O缓冲区也占用非分页内存,以4KB大小作为一个块(4 KB正好是x86内存管理单元的页面大小)。因此,一个最简单的在单一套接字上规律收发数据的应用程序至少要占用10KB大小的非分页内存。简单假设每个连接占用10KB内存,那么理论上,NT 4.0最多可打开约12,800个套接字,Win2K最多可打开约25,600个。

       我曾见过这样的报导:内存64 MBWindows NT 4.0主机最多处理了1,500个连接, 内存128 MB的主机大约处理了4,000个连接,内存1192 MB的主机最多处理了4,700个连接。可以推算出,在这些主机上每个连接使用了4 KB~6 KB的内存。这不同于上文中所说的10KB,原因可能如下:在服务端程序中,并不是所有连接都在不停地收发数据。一个空闲连接只需要2KB的内存大小。

       因此,把“平均”大小调整到6KB每套接字,可推算出,NT 4.0大约可处理22,000个套接字,Win2K 大约可处理44,000个套接字。 我知道的报导过的在Windows NT 4.0上的最大值是16,000个套接字,这个实际值偏低,可能部分是由于整个非分页内存池并非仅供一个程序使用,其它正在运行的程序(比如OS内核服务)将和你的程序抢占非分页内存池的空间。

 

4.9 -什么是“64 sockets”限制?

      存在两个“64 sockets”限制:

      Windows的事件机制(如WaitForMultipleObjects()) 只能同时等待64个对象。Winsock 2 提供了WSAEventSelect()函数,使得你可利用Windows的事件机制在套接字上等待网络事件。因为这用到了Windows的事件机制,所以你只能同时在64个套接字上等待网络事件。如果你想要同时在多于64个的Winsock事件对象上等待,你必须使用多个线程,每个线程等待的套接字不超过64个。

      select()函数在某些情况下也被限制在同时只能等待64个套接字。Winsock头文件中定义的FD_SETSIZE常量决定了传递给select()函数的fd_set结构体的大小。FD_SETSIZE的默认值为64,但如果你在包含Winsock头文件之前定义了一个不同的值,那么Winsock将使用你所定义的这个值:

            #define FD_SETSIZE 1024

            #include <wsock32.h>

       问题是当前的网络协议很复杂,它由许多部分组成而且组件来源多样化,通过分层服务提供者(LSP)等机制可将第三方组件包含进来。当你改变FD_SETSIZE这个常量的值时,需要所有组件都能在新的条件下顺利运行。我们希望如此,但并不总是如此。最典型的症状就是对于较大的fd_set结构体,某些组件会忽略64个套接字之外的其它套接字。同事件对象的情形一样,可使用多线程来突破这一限制。

 

4.10 -如何让Winsock使用特定的网络接口?

      在我回答上述问题之前,请先记住协议的路由层正是为解决这一问题而存在的。如果你的系统没有按你的要求进行工作,可能你只需要改变一下路由表(可通过route netstat命令行程序来实现)即可。

      你强迫协议使用特定网络接口的原因一般有两个。一个原因是,你希望服务端程序只在特定接口上处理入网连接。比如,主机的一个接口是连接着私有局域网的以太网卡,另一个接口是连接着危险的互联网的USB DSL调制解调器。在这种情况下,如果可以,只在可信网络上进行监听会更安全一些。另一个原因是,你有两个或更多可选择的出口路由,你希望你的客户端程序连接到特定的出口而不需要路由层来干预。

      这两件事都可通过bind()函数解决。你可使用某个“get my IP addresses”示例程序(examples,向用户展现一个地址列表,然后用户选择一个合适的地址供后续使用----你的程序将在bing()中使用这个选定的地址。显然,这种方法只适用于面向高级用户的程序。

      现代(Modern版本的Windows允许你在网络控制面板上为一个网络接口设置多个IP地址。打开高级配置,那里有更多的CP/IP设置项,你可找到能为单一接口输入多个IP地址的地方。我最近一次尝试时,工作站和家庭版本的Windows限制你只能填写5个地址,而服务器版本的Windows系统对此没有限制。

      补充:互联网主机托管公司提供虚拟共享主机的方法之一就涉及到“为一个网络接口添加多个IP别名”这一技术。该服务器上托管的每个站点被分配其中的一个IP地址,Web服务器分别在每个IP上进行监听。这使得Web 服务器能探测到进入的连接是从哪个IP地址进来的,从而决定该为其提供哪个网站的网页。在外部客户机看来,一个物理服务器就这样变成了许多个“服务器”。虚拟主机托管还有其它几种方法,这里就不深究了,言归正传。

 

4.11 - FIN_WAIT_x, TIME_WAIT, CLOSE_WAIT 和其它状态意味着什么?

    netstat工具的显示结果中可看到这些套接字状态。关于它们的含义和作用,请参考本系列文章Debugging TCP/IP

 

4.12 -什么是 { SYN, ACK, FIN, RST } 比特位?

    请参考本系列文章之Debugging TCP/IP

4.13 - 在客户端程序中调用bind()来绑定特定端口是不明智的吗?

     这样做,在极少数情况下是可以的,但大部分时间是不明智的。我只听说过两个好的应用例子:

     某些协议要求客户连接来自于特定范围内的端口号。出于安全考虑,Berkeley的部分“r-命令” (即: rlogin, rsh, rcp)就是这样实现的。因为在现代操作系统上只有特权用户才能绑定到较小编号(1-1023) 的端口上,所以来自此类端口的连接暗示了远程用户是一个特权用户。这正是r-命令方案中针对安全而设计的小点之一,如果当前连接来自较小的端口号,那么服务端程序就相信那些声称自己是特权用户的远程用户。(这些协议在其它方面是极为不安全的,因此在有一个精明的系统管理员的系统上不再被使用了)。这些命令程序的实现方法是挨个尝试每个端口直到绑定成功为止。虽然这里是从Unix的角度来说的,但也同样适用于现代版本的Windows系统,在这些系统中普通用户不能像管理员用户那样畅通无阻。

      另一个常见例子是“主动”模式的FTP:客户端绑定到一个随机端口上,然后告知服务端连接到这个端口上进行后续的数据传输。这样做是有道理的,这辩证地简化了协议,而且FTP客户端不必绑定到特定的端口上,它只需要绑定一个端口就可以了。(偶然情况下,会绑定到0号端口上----在实现过程中,是由协议来负责选择空闲端口号的)。另外,在这种情况下,FTP客户端实际上担当了服务端的角色,因此它必须绑定到一个端口上,这也是合情合理的。

      注意:在这两个例子中,我们都没有试图绑定到特定的端口上。对客户端而言,这是好的设计。两个例子的客户端都可以灵活地选择要绑定的端口号,因为就本质而言,客户端连接一般都是短暂的,而服务端往往会长时间连续运行,经常和物理机的运行时间一样长。同短暂运行的进程相比,持久运行的进程更有理由绑定到某个特定端口上,因为如果访问该端口失败,系统管理员可更准确地定位和解决问题。一个应该一直运行的程序可能启动失败,如果你重新启动这个服务端程序,那么就会再次失败,因为这个引起冲突的程序还将再次绑定同一个端口。如果在客户端出现了端口冲突的情况,一般不会每次都出现,即使每次都出现,那也只会出现在客户端运行的时候。因此调试网络程序需要更多技巧。

      在客户端程序中绑定特定端口并非明智之举的另一个原因是:想想web浏览器。它通常创建好几个连接来下载一个网页,每一个连接负责获取一个独立的部分:图像,小程序,音频剪辑等。浏览器经常开着多个连接并且都连接到同一个服务器上,这使得多个下载任务可并行执行。如果Web浏览器被绑定到特定端口上,并行下载就不行了,因为在同一时刻只能有一个连接存在,或者取决于它的状态,甚至在同一时刻只能有一个web浏览器实例在运行。

      除此之外,还有一个问题。当你关闭一个TCP连接时,该连接将进入短暂的TIME_WAIT(一般为30~120秒)状态,在此期间,你不能重复利用该连接的"5元组"{本地主机,本地端口,协议,原地主机,原地端口}。(这个超时阶段是所有被正确实现的TCP/IP协议的特征之一,详见RFC 793 ,尤其是RFC 1122)。 实际上,这意味着如果你一直绑定到某个特定端口,那么在TIME_WAIT 到期之前,你不能再使用同一远地端口来连接同一主机。我亲眼见过不出现TIME_WAIT状态的异常情况,这是协议BUG,是靠不住的。

关于这个问题的更多信息,请参考Lame List

 

4.14 -什么是连接储备队列backlog)

      当一个连接请求到达网络协议network stack时,协议首先检查是否有程序正在请求的端口上进行监听。如果有,协议就响应远端主机(remote peer,完成本次连接请求。协议在一个称为储备队列的地方保存这个连接信息。(当储备队列中有连接时,accept()调用变得很简单,协议从连接储备队列中移除最旧的那个连接并返回一个对应的套接字。)

      listen()函数中有一个参数可以为特定套接字设置连接储备队列的尺寸。当这个队列满了的时候,协议就会拒绝连接请求。

      如果你的程序在接收新的连接时需要量力而行,那么拒绝连接是很有用的。如果你的程序已竭尽全力了,而储备队列还是被填满了,这就意味着服务端已达到负载极限了。如果协议再接受更多的连接,你的程序将无法正常处理它们,那么客户端会以为你的服务端挂掉了。如果拒绝连接,那么客户端至少可以知道服务端现在太忙了,它可稍后再尝试连接。

       listen()函数中“储备队列尺寸”这一参数的最佳值取决于你希望在两次accept()调用之间看到多少连接?假设你希望平均值是每秒1000个连接,峰值为每秒3000个连接(注意:我选取这些数值是因为它们容易计算,并不代表实际情况!)。如果用一个小的连接储备队列来处理峰值负载,那么服务端程序调用accept() 函数的时间间隔必须小于0.3毫秒。假设你已根据负载测量出了accept的调用时间间隔为0.8毫秒:这对于处理平均连接负载是够快了,但对于处理峰值负载就太慢了。这种情况下,你可把储备队列设置得大一些,从而使协议在峰值情况下可储存更多连接。如果峰值情况转瞬即逝的话,你的程序很快就会跟上趟并清空连接储备队列。

       传统情况下,listen()函数中“储备队列尺寸”这一参数的值为5。这确实是工作站版本的windows系统的极限值,但在服务器版本的Windows系统上,如果没有使用动态储备队列(见下文),那么连接储备队列的最大尺寸是200。如果你传递了一个更大的值,协议也只会使用它自身的最大储备队列尺寸。所以,你可向listen()传递一个特殊的常量SOMAXCONN,对应的值是0x7FFFFFFF,这表示让协议使用当前平台下的最大值,而不管这个值到底是多大。如果协议忽略了你的请求值,那么通过一般手段是无法得知协议选择的储备队列是多大的。

       如果你的程序可快速调用accept(),那么小储备队列就不是什么问题。但是,这确实意味着短时间内的大量连接尝试会填满储备队列。所以,不是为服务器设计的Windows版本不适宜选作高负载的服务器:在这种平台上,合法的负载或者SYN洪攻击(见下文)都会导致服务器超载。

       注意:大储备队列会使SYN洪攻击变得更加有效。Winsock创建储备队列后,该队列起初很小,然后慢慢增长。因为储备队列占用的是非分页系统内存,所以SYN洪攻击可致使该队列吞噬掉大量宝贵的内存资源。

       1996年,SYN洪攻击首次出现后,微软向Windows NT 4.0 SP3中添加一个称为"动态储备队列"的功能。为了向后兼容,这一功能通常都被关闭了,但是一旦你启用它,协议将根据网络情况扩大或减小连接储备队列的尺寸(为了阻挡恶意的SYN洪攻击,甚至可使储备队列大于200这个正常的上限值)。微软知识库中关于动态储备队列的文章也对连接储备队列作了很有意义的讨论。

       你可能已经发现了:在储备队列很大或者很小的情况下,SYN洪攻击对系统的危害性都很大。如果你希望你的服务器能够承受住SYN洪攻击,那么选择一个中间值是最明智的。要么使用微软的动态储备队列功能,要么在20-200之间选择一个值并根据需要调整这个值。

程序可能过分依赖储备队列。假设有一个单线程阻塞模式的服务端程序:这种设计意味着只能并发处理一个连接。然而,它可能设置一个很大的储备队列,导致协议会接收并储存所有连接直到程序处理下一个连接为止。(参考示例,看看这种技术是如何工作的)。除非你的连接速度很慢而且连接次数很少,否则上述设计不会从储备队列上获得什么好处。(这只是教学式的假设。任何一个设计这样程序的程序员都可能会在只有一个客户连接的时候,使用10作为储备队列的大小,甚至关闭监听套接字。)